diff options
| author | Dante Niewenhuis <d.niewenhuis@hotmail.com> | 2023-11-10 22:30:49 +0100 |
|---|---|---|
| committer | Dante Niewenhuis <d.niewenhuis@hotmail.com> | 2023-11-10 22:32:29 +0100 |
| commit | 059a09949e839fe9e906c398710b5235b452b0e0 (patch) | |
| tree | 6d8fdca4db4697b3d5dbe6ae291425d07fa84276 | |
| parent | aa9149b9c49be570c2b14254d3c6a23d7c077e34 (diff) | |
added greenifier demo, fixed HostTableReader
49 files changed, 3511 insertions, 118 deletions
diff --git a/opendc-experiments/opendc-experiments-compute/src/main/kotlin/org/opendc/experiments/compute/TraceHelpers.kt b/opendc-experiments/opendc-experiments-compute/src/main/kotlin/org/opendc/experiments/compute/TraceHelpers.kt index 16d28edb..5d4c88cd 100644 --- a/opendc-experiments/opendc-experiments-compute/src/main/kotlin/org/opendc/experiments/compute/TraceHelpers.kt +++ b/opendc-experiments/opendc-experiments-compute/src/main/kotlin/org/opendc/experiments/compute/TraceHelpers.kt @@ -104,7 +104,7 @@ public suspend fun ComputeService.replay( // Wait for the server reach its end time val endTime = entry.stopTime.toEpochMilli() - delay(endTime + workloadOffset - clock.millis() + 5 * 60 * 1000) + delay(endTime + workloadOffset - clock.millis() + (5 * 60 * 10000)) // Stop the server after reaching the end-time of the virtual machine server.stop() diff --git a/opendc-experiments/opendc-experiments-compute/src/main/kotlin/org/opendc/experiments/compute/export/parquet/ParquetHostDataWriter.kt b/opendc-experiments/opendc-experiments-compute/src/main/kotlin/org/opendc/experiments/compute/export/parquet/ParquetHostDataWriter.kt index 979aebeb..75e792d0 100644 --- a/opendc-experiments/opendc-experiments-compute/src/main/kotlin/org/opendc/experiments/compute/export/parquet/ParquetHostDataWriter.kt +++ b/opendc-experiments/opendc-experiments-compute/src/main/kotlin/org/opendc/experiments/compute/export/parquet/ParquetHostDataWriter.kt @@ -78,68 +78,80 @@ public class ParquetHostDataWriter(path: File, bufferSize: Int) : consumer.addBinary(UUID.fromString(data.host.id).toBinary()) consumer.endField("host_id", 1) - consumer.startField("uptime", 2) - consumer.addLong(data.uptime) - consumer.endField("uptime", 2) + consumer.startField("cpu_count", 2) + consumer.addInteger(data.host.cpuCount) + consumer.endField("cpu_count", 2) - consumer.startField("downtime", 3) - consumer.addLong(data.downtime) - consumer.endField("downtime", 3) + consumer.startField("mem_capacity", 3) + consumer.addLong(data.host.memCapacity) + consumer.endField("mem_capacity", 3) - val bootTime = data.bootTime - if (bootTime != null) { - consumer.startField("boot_time", 4) - consumer.addLong(bootTime.toEpochMilli()) - consumer.endField("boot_time", 4) - } + consumer.startField("guests_terminated", 4) + consumer.addInteger(data.guestsTerminated) + consumer.endField("guests_terminated", 4) - consumer.startField("cpu_count", 5) - consumer.addInteger(data.host.cpuCount) - consumer.endField("cpu_count", 5) + consumer.startField("guests_running", 5) + consumer.addInteger(data.guestsRunning) + consumer.endField("guests_running", 5) - consumer.startField("cpu_limit", 6) + consumer.startField("guests_error", 6) + consumer.addInteger(data.guestsError) + consumer.endField("guests_error", 6) + + consumer.startField("guests_invalid", 7) + consumer.addInteger(data.guestsInvalid) + consumer.endField("guests_invalid", 7) + + consumer.startField("cpu_limit", 8) consumer.addDouble(data.cpuLimit) - consumer.endField("cpu_limit", 6) + consumer.endField("cpu_limit", 8) + + consumer.startField("cpu_usage", 9) + consumer.addDouble(data.cpuUsage) + consumer.endField("cpu_usage", 9) + + consumer.startField("cpu_demand", 10) + consumer.addDouble(data.cpuUsage) + consumer.endField("cpu_demand", 10) + + consumer.startField("cpu_utilization", 11) + consumer.addDouble(data.cpuUtilization) + consumer.endField("cpu_utilization", 11) - consumer.startField("cpu_time_active", 7) + consumer.startField("cpu_time_active", 12) consumer.addLong(data.cpuActiveTime) - consumer.endField("cpu_time_active", 7) + consumer.endField("cpu_time_active", 12) - consumer.startField("cpu_time_idle", 8) + consumer.startField("cpu_time_idle", 13) consumer.addLong(data.cpuIdleTime) - consumer.endField("cpu_time_idle", 8) + consumer.endField("cpu_time_idle", 13) - consumer.startField("cpu_time_steal", 9) + consumer.startField("cpu_time_steal", 14) consumer.addLong(data.cpuStealTime) - consumer.endField("cpu_time_steal", 9) + consumer.endField("cpu_time_steal", 14) - consumer.startField("cpu_time_lost", 10) + consumer.startField("cpu_time_lost", 15) consumer.addLong(data.cpuLostTime) - consumer.endField("cpu_time_lost", 10) + consumer.endField("cpu_time_lost", 15) - consumer.startField("mem_limit", 11) - consumer.addLong(data.host.memCapacity) - consumer.endField("mem_limit", 11) - - consumer.startField("power_total", 12) + consumer.startField("power_total", 16) consumer.addDouble(data.powerTotal) - consumer.endField("power_total", 12) + consumer.endField("power_total", 16) - consumer.startField("guests_terminated", 13) - consumer.addInteger(data.guestsTerminated) - consumer.endField("guests_terminated", 13) - - consumer.startField("guests_running", 14) - consumer.addInteger(data.guestsRunning) - consumer.endField("guests_running", 14) + consumer.startField("uptime", 17) + consumer.addLong(data.uptime) + consumer.endField("uptime", 17) - consumer.startField("guests_error", 15) - consumer.addInteger(data.guestsError) - consumer.endField("guests_error", 15) + consumer.startField("downtime", 18) + consumer.addLong(data.downtime) + consumer.endField("downtime", 18) - consumer.startField("guests_invalid", 16) - consumer.addInteger(data.guestsInvalid) - consumer.endField("guests_invalid", 16) + val bootTime = data.bootTime + if (bootTime != null) { + consumer.startField("boot_time", 19) + consumer.addLong(bootTime.toEpochMilli()) + consumer.endField("boot_time", 19) + } consumer.endMessage() } @@ -162,22 +174,36 @@ public class ParquetHostDataWriter(path: File, bufferSize: Int) : .`as`(LogicalTypeAnnotation.uuidType()) .named("host_id"), Types - .required(PrimitiveType.PrimitiveTypeName.INT64) - .named("uptime"), + .required(PrimitiveType.PrimitiveTypeName.INT32) + .named("cpu_count"), Types .required(PrimitiveType.PrimitiveTypeName.INT64) - .named("downtime"), + .named("mem_capacity"), Types - .optional(PrimitiveType.PrimitiveTypeName.INT64) - .`as`(LogicalTypeAnnotation.timestampType(true, LogicalTypeAnnotation.TimeUnit.MILLIS)) - .named("boot_time"), + .required(PrimitiveType.PrimitiveTypeName.INT32) + .named("guests_terminated"), Types .required(PrimitiveType.PrimitiveTypeName.INT32) - .named("cpu_count"), + .named("guests_running"), + Types + .required(PrimitiveType.PrimitiveTypeName.INT32) + .named("guests_error"), + Types + .required(PrimitiveType.PrimitiveTypeName.INT32) + .named("guests_invalid"), Types .required(PrimitiveType.PrimitiveTypeName.DOUBLE) .named("cpu_limit"), Types + .required(PrimitiveType.PrimitiveTypeName.DOUBLE) + .named("cpu_usage"), + Types + .required(PrimitiveType.PrimitiveTypeName.DOUBLE) + .named("cpu_demand"), + Types + .required(PrimitiveType.PrimitiveTypeName.DOUBLE) + .named("cpu_utilization"), + Types .required(PrimitiveType.PrimitiveTypeName.INT64) .named("cpu_time_active"), Types @@ -190,23 +216,18 @@ public class ParquetHostDataWriter(path: File, bufferSize: Int) : .required(PrimitiveType.PrimitiveTypeName.INT64) .named("cpu_time_lost"), Types - .required(PrimitiveType.PrimitiveTypeName.INT64) - .named("mem_limit"), - Types .required(PrimitiveType.PrimitiveTypeName.DOUBLE) .named("power_total"), Types - .required(PrimitiveType.PrimitiveTypeName.INT32) - .named("guests_terminated"), - Types - .required(PrimitiveType.PrimitiveTypeName.INT32) - .named("guests_running"), + .required(PrimitiveType.PrimitiveTypeName.INT64) + .named("uptime"), Types - .required(PrimitiveType.PrimitiveTypeName.INT32) - .named("guests_error"), + .required(PrimitiveType.PrimitiveTypeName.INT64) + .named("downtime"), Types - .required(PrimitiveType.PrimitiveTypeName.INT32) - .named("guests_invalid") + .optional(PrimitiveType.PrimitiveTypeName.INT64) + .`as`(LogicalTypeAnnotation.timestampType(true, LogicalTypeAnnotation.TimeUnit.MILLIS)) + .named("boot_time") ) .named("host") } diff --git a/opendc-experiments/opendc-experiments-compute/src/main/kotlin/org/opendc/experiments/compute/export/parquet/ParquetServerDataWriter.kt b/opendc-experiments/opendc-experiments-compute/src/main/kotlin/org/opendc/experiments/compute/export/parquet/ParquetServerDataWriter.kt index 99ef83c6..ed5bb64f 100644 --- a/opendc-experiments/opendc-experiments-compute/src/main/kotlin/org/opendc/experiments/compute/export/parquet/ParquetServerDataWriter.kt +++ b/opendc-experiments/opendc-experiments-compute/src/main/kotlin/org/opendc/experiments/compute/export/parquet/ParquetServerDataWriter.kt @@ -86,55 +86,55 @@ public class ParquetServerDataWriter(path: File, bufferSize: Int) : consumer.endField("host_id", 2) } - consumer.startField("uptime", 3) - consumer.addLong(data.uptime) - consumer.endField("uptime", 3) - - consumer.startField("downtime", 4) - consumer.addLong(data.downtime) - consumer.endField("downtime", 4) - - val bootTime = data.bootTime - if (bootTime != null) { - consumer.startField("boot_time", 5) - consumer.addLong(bootTime.toEpochMilli()) - consumer.endField("boot_time", 5) - } - - val provisionTime = data.provisionTime - if (provisionTime != null) { - consumer.startField("provision_time", 6) - consumer.addLong(provisionTime.toEpochMilli()) - consumer.endField("provision_time", 6) - } + consumer.startField("mem_capacity", 3) + consumer.addLong(data.server.memCapacity) + consumer.endField("mem_capacity", 3) - consumer.startField("cpu_count", 7) + consumer.startField("cpu_count", 4) consumer.addInteger(data.server.cpuCount) - consumer.endField("cpu_count", 7) + consumer.endField("cpu_count", 4) - consumer.startField("cpu_limit", 8) + consumer.startField("cpu_limit", 5) consumer.addDouble(data.cpuLimit) - consumer.endField("cpu_limit", 8) + consumer.endField("cpu_limit", 5) - consumer.startField("cpu_time_active", 9) + consumer.startField("cpu_time_active", 6) consumer.addLong(data.cpuActiveTime) - consumer.endField("cpu_time_active", 9) + consumer.endField("cpu_time_active", 6) - consumer.startField("cpu_time_idle", 10) + consumer.startField("cpu_time_idle", 7) consumer.addLong(data.cpuIdleTime) - consumer.endField("cpu_time_idle", 10) + consumer.endField("cpu_time_idle", 7) - consumer.startField("cpu_time_steal", 11) + consumer.startField("cpu_time_steal", 8) consumer.addLong(data.cpuStealTime) - consumer.endField("cpu_time_steal", 11) + consumer.endField("cpu_time_steal", 8) - consumer.startField("cpu_time_lost", 12) + consumer.startField("cpu_time_lost", 9) consumer.addLong(data.cpuLostTime) - consumer.endField("cpu_time_lost", 12) + consumer.endField("cpu_time_lost", 9) - consumer.startField("mem_limit", 13) - consumer.addLong(data.server.memCapacity) - consumer.endField("mem_limit", 13) + consumer.startField("uptime", 10) + consumer.addLong(data.uptime) + consumer.endField("uptime", 10) + + consumer.startField("downtime", 11) + consumer.addLong(data.downtime) + consumer.endField("downtime", 11) + + val provisionTime = data.provisionTime + if (provisionTime != null) { + consumer.startField("provision_time", 12) + consumer.addLong(provisionTime.toEpochMilli()) + consumer.endField("provision_time", 12) + } + + val bootTime = data.bootTime + if (bootTime != null) { + consumer.startField("boot_time", 13) + consumer.addLong(bootTime.toEpochMilli()) + consumer.endField("boot_time", 13) + } consumer.endMessage() } @@ -162,18 +162,7 @@ public class ParquetServerDataWriter(path: File, bufferSize: Int) : .named("host_id"), Types .required(PrimitiveType.PrimitiveTypeName.INT64) - .named("uptime"), - Types - .required(PrimitiveType.PrimitiveTypeName.INT64) - .named("downtime"), - Types - .optional(PrimitiveType.PrimitiveTypeName.INT64) - .`as`(LogicalTypeAnnotation.timestampType(true, LogicalTypeAnnotation.TimeUnit.MILLIS)) - .named("provision_time"), - Types - .optional(PrimitiveType.PrimitiveTypeName.INT64) - .`as`(LogicalTypeAnnotation.timestampType(true, LogicalTypeAnnotation.TimeUnit.MILLIS)) - .named("boot_time"), + .named("mem_capacity"), Types .required(PrimitiveType.PrimitiveTypeName.INT32) .named("cpu_count"), @@ -194,7 +183,19 @@ public class ParquetServerDataWriter(path: File, bufferSize: Int) : .named("cpu_time_lost"), Types .required(PrimitiveType.PrimitiveTypeName.INT64) - .named("mem_limit") + .named("uptime"), + Types + .required(PrimitiveType.PrimitiveTypeName.INT64) + .named("downtime"), + Types + .optional(PrimitiveType.PrimitiveTypeName.INT64) + .`as`(LogicalTypeAnnotation.timestampType(true, LogicalTypeAnnotation.TimeUnit.MILLIS)) + .named("provision_time"), + Types + .optional(PrimitiveType.PrimitiveTypeName.INT64) + .`as`(LogicalTypeAnnotation.timestampType(true, LogicalTypeAnnotation.TimeUnit.MILLIS)) + .named("boot_time"), + ) .named("server") } diff --git a/opendc-experiments/opendc-experiments-compute/src/main/kotlin/org/opendc/experiments/compute/telemetry/ComputeMetricReader.kt b/opendc-experiments/opendc-experiments-compute/src/main/kotlin/org/opendc/experiments/compute/telemetry/ComputeMetricReader.kt index efd38a3c..de0f0b7c 100644 --- a/opendc-experiments/opendc-experiments-compute/src/main/kotlin/org/opendc/experiments/compute/telemetry/ComputeMetricReader.kt +++ b/opendc-experiments/opendc-experiments-compute/src/main/kotlin/org/opendc/experiments/compute/telemetry/ComputeMetricReader.kt @@ -95,7 +95,7 @@ public class ComputeMetricReader( for (host in service.hosts) { val reader = hostTableReaders.computeIfAbsent(host) { HostTableReaderImpl(it) } reader.record(now) - monitor.record(reader) + monitor.record(reader.copy()) reader.reset() } @@ -185,6 +185,34 @@ public class ComputeMetricReader( * An aggregator for host metrics before they are reported. */ private class HostTableReaderImpl(host: Host) : HostTableReader { + override fun copy(): HostTableReader { + val newHostTable = HostTableReaderImpl(_host) + newHostTable.setValues(this) + + return newHostTable + } + + override fun setValues(table: HostTableReader) { + _timestamp = table.timestamp + _guestsTerminated = table.guestsTerminated + _guestsRunning = table.guestsRunning + _guestsError = table.guestsError + _guestsInvalid = table.guestsInvalid + _cpuLimit = table.cpuLimit + _cpuDemand = table.cpuDemand + _cpuUsage = table.cpuUsage + _cpuUtilization = table.cpuUtilization + _cpuActiveTime = table.cpuActiveTime + _cpuIdleTime = table.cpuIdleTime + _cpuStealTime = table.cpuStealTime + _cpuLostTime = table.cpuLostTime + _powerUsage = table.powerUsage + _powerTotal = table.powerTotal + _uptime = table.uptime + _downtime = table.downtime + _bootTime = table.bootTime + } + private val _host = host override val host: HostInfo = HostInfo(host.uid.toString(), host.name, "x86", host.model.cpuCount, host.model.memoryCapacity) @@ -326,6 +354,28 @@ public class ComputeMetricReader( * An aggregator for server metrics before they are reported. */ private class ServerTableReaderImpl(private val service: ComputeService, server: Server) : ServerTableReader { + override fun copy(): ServerTableReader { + val newServerTable = ServerTableReaderImpl(service, _server) + newServerTable.setValues(this) + + return newServerTable + } + + override fun setValues(table: ServerTableReader) { + _timestamp = table.timestamp + _uptime = table.uptime + _downtime = table.downtime + _provisionTime = table.provisionTime + _bootTime = table.bootTime + _cpuLimit = table.cpuLimit + _cpuActiveTime = table.cpuActiveTime + _cpuIdleTime = table.cpuIdleTime + _cpuStealTime = table.cpuStealTime + _cpuLostTime = table.cpuLostTime + + host = table.host + } + private val _server = server /** diff --git a/opendc-experiments/opendc-experiments-compute/src/main/kotlin/org/opendc/experiments/compute/telemetry/table/HostTableReader.kt b/opendc-experiments/opendc-experiments-compute/src/main/kotlin/org/opendc/experiments/compute/telemetry/table/HostTableReader.kt index e6953550..9af4d13e 100644 --- a/opendc-experiments/opendc-experiments-compute/src/main/kotlin/org/opendc/experiments/compute/telemetry/table/HostTableReader.kt +++ b/opendc-experiments/opendc-experiments-compute/src/main/kotlin/org/opendc/experiments/compute/telemetry/table/HostTableReader.kt @@ -28,10 +28,10 @@ import java.time.Instant * An interface that is used to read a row of a host trace entry. */ public interface HostTableReader { - /** - * The timestamp of the current entry of the reader. - */ - public val timestamp: Instant + + public fun copy() : HostTableReader + + public fun setValues(table: HostTableReader) /** * The [HostInfo] of the host to which the row belongs to. @@ -39,6 +39,11 @@ public interface HostTableReader { public val host: HostInfo /** + * The timestamp of the current entry of the reader. + */ + public val timestamp: Instant + + /** * The number of guests that are in a terminated state. */ public val guestsTerminated: Int diff --git a/opendc-experiments/opendc-experiments-compute/src/main/kotlin/org/opendc/experiments/compute/telemetry/table/ServerTableReader.kt b/opendc-experiments/opendc-experiments-compute/src/main/kotlin/org/opendc/experiments/compute/telemetry/table/ServerTableReader.kt index c4e2fb4c..c6d8f1e1 100644 --- a/opendc-experiments/opendc-experiments-compute/src/main/kotlin/org/opendc/experiments/compute/telemetry/table/ServerTableReader.kt +++ b/opendc-experiments/opendc-experiments-compute/src/main/kotlin/org/opendc/experiments/compute/telemetry/table/ServerTableReader.kt @@ -28,6 +28,10 @@ import java.time.Instant * An interface that is used to read a row of a server trace entry. */ public interface ServerTableReader { + + public fun copy(): ServerTableReader + + public fun setValues(table: ServerTableReader) /** * The timestamp of the current entry of the reader. */ diff --git a/opendc-experiments/opendc-experiments-greenifier/build.gradle.kts b/opendc-experiments/opendc-experiments-greenifier/build.gradle.kts new file mode 100644 index 00000000..d75abe49 --- /dev/null +++ b/opendc-experiments/opendc-experiments-greenifier/build.gradle.kts @@ -0,0 +1,90 @@ +/* + * Copyright (c) 2019 AtLarge Research + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ + +description = "Experiments for the Greenifier work" + +/* Build configuration */ +plugins { + `kotlin-conventions` + `testing-conventions` + `jacoco-conventions` + `benchmark-conventions` + distribution +} + +dependencies { + api(projects.opendcExperiments.opendcExperimentsCompute) + + implementation(projects.opendcSimulator.opendcSimulatorCore) + implementation(projects.opendcSimulator.opendcSimulatorCompute) + implementation(projects.opendcCompute.opendcComputeSimulator) + + implementation(libs.clikt) + implementation(libs.progressbar) + implementation(libs.kotlin.logging) + implementation(libs.jackson.dataformat.csv) + + runtimeOnly(projects.opendcTrace.opendcTraceOpendc) + runtimeOnly(libs.log4j.core) + runtimeOnly(libs.log4j.slf4j) +} + +val createGreenifierApp by tasks.creating(CreateStartScripts::class) { + dependsOn(tasks.jar) + + applicationName = "greenifier" + mainClass.set("org.opendc.experiments.greenifier.GreenifierCli") + classpath = tasks.jar.get().outputs.files + configurations["runtimeClasspath"] + outputDir = project.buildDir.resolve("scripts") +} + +/* Create custom Greenifier distribution */ +distributions { + main { + distributionBaseName.set("greenifier") + + contents { + from("README.md") + from("LICENSE.txt") + from("../../LICENSE.txt") { + rename { "LICENSE-OpenDC.txt" } + } + + into("bin") { + from(createGreenifierApp) + } + + into("lib") { + from(tasks.jar) + from(configurations["runtimeClasspath"]) + } + + into("resources") { + from("src/main/resources") + } + + into("Python_scripts") { + from("src/main/Python_scripts") + } + } + } +} diff --git a/opendc-experiments/opendc-experiments-greenifier/src/jmh/kotlin/org/opendc/experiments/capelin/GreenifierBenchmarks.kt b/opendc-experiments/opendc-experiments-greenifier/src/jmh/kotlin/org/opendc/experiments/capelin/GreenifierBenchmarks.kt new file mode 100644 index 00000000..7f06f41a --- /dev/null +++ b/opendc-experiments/opendc-experiments-greenifier/src/jmh/kotlin/org/opendc/experiments/capelin/GreenifierBenchmarks.kt @@ -0,0 +1,93 @@ +/* + * Copyright (c) 2021 AtLarge Research + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ + +package org.opendc.experiments.greenifier + +import org.opendc.compute.service.ComputeService +import org.opendc.compute.service.scheduler.FilterScheduler +import org.opendc.compute.service.scheduler.filters.ComputeFilter +import org.opendc.compute.service.scheduler.filters.RamFilter +import org.opendc.compute.service.scheduler.filters.VCpuFilter +import org.opendc.compute.service.scheduler.weights.CoreRamWeigher +import org.opendc.experiments.greenifier.topology.clusterTopology +import org.opendc.experiments.compute.ComputeWorkloadLoader +import org.opendc.experiments.compute.VirtualMachine +import org.opendc.experiments.compute.replay +import org.opendc.experiments.compute.setupComputeService +import org.opendc.experiments.compute.setupHosts +import org.opendc.experiments.compute.topology.HostSpec +import org.opendc.experiments.compute.trace +import org.opendc.experiments.provisioner.Provisioner +import org.opendc.simulator.kotlin.runSimulation +import org.openjdk.jmh.annotations.Benchmark +import org.openjdk.jmh.annotations.Fork +import org.openjdk.jmh.annotations.Measurement +import org.openjdk.jmh.annotations.Param +import org.openjdk.jmh.annotations.Scope +import org.openjdk.jmh.annotations.Setup +import org.openjdk.jmh.annotations.State +import org.openjdk.jmh.annotations.Warmup +import java.io.File +import java.util.Random +import java.util.concurrent.TimeUnit + +/** + * Benchmark suite for the Greenifier experiments. + */ +@State(Scope.Thread) +@Fork(1) +@Warmup(iterations = 2, time = 5, timeUnit = TimeUnit.SECONDS) +@Measurement(iterations = 5, time = 5, timeUnit = TimeUnit.SECONDS) +class GreenifierBenchmarks { + private lateinit var vms: List<VirtualMachine> + private lateinit var topology: List<HostSpec> + + @Param("true", "false") + private var isOptimized: Boolean = false + + @Setup + fun setUp() { + val loader = ComputeWorkloadLoader(File("src/test/resources/trace")) + vms = trace("bitbrains-small").resolve(loader, Random(1L)) + topology = checkNotNull(object {}.javaClass.getResourceAsStream("/topology.txt")).use { clusterTopology(it) } + } + + @Benchmark + fun benchmarkGreenifier() = runSimulation { + val serviceDomain = "compute.opendc.org" + + Provisioner(dispatcher, seed = 0).use { provisioner -> + val computeScheduler = FilterScheduler( + filters = listOf(ComputeFilter(), VCpuFilter(16.0), RamFilter(1.0)), + weighers = listOf(CoreRamWeigher(multiplier = 1.0)) + ) + + provisioner.runSteps( + setupComputeService(serviceDomain, { computeScheduler }), + setupHosts(serviceDomain, topology, optimize = isOptimized) + ) + + val service = provisioner.registry.resolve(serviceDomain, ComputeService::class.java)!! + service.replay(timeSource, vms, 0L, interference = true) + } + } +} diff --git a/opendc-experiments/opendc-experiments-greenifier/src/jmh/resources/log4j2.xml b/opendc-experiments/opendc-experiments-greenifier/src/jmh/resources/log4j2.xml new file mode 100644 index 00000000..c496dd75 --- /dev/null +++ b/opendc-experiments/opendc-experiments-greenifier/src/jmh/resources/log4j2.xml @@ -0,0 +1,37 @@ +<?xml version="1.0" encoding="UTF-8"?> +<!-- + ~ MIT License + ~ + ~ Copyright (c) 2020 atlarge-research + ~ + ~ Permission is hereby granted, free of charge, to any person obtaining a copy + ~ of this software and associated documentation files (the "Software"), to deal + ~ in the Software without restriction, including without limitation the rights + ~ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + ~ copies of the Software, and to permit persons to whom the Software is + ~ furnished to do so, subject to the following conditions: + ~ + ~ The above copyright notice and this permission notice shall be included in all + ~ copies or substantial portions of the Software. + ~ + ~ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + ~ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + ~ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + ~ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + ~ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + ~ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + ~ SOFTWARE. + --> + +<Configuration status="WARN"> + <Appenders> + <Console name="Console" target="SYSTEM_OUT"> + <PatternLayout pattern="%d{HH:mm:ss.SSS} [%highlight{%-5level}] %logger{36} - %msg%n" disableAnsi="false"/> + </Console> + </Appenders> + <Loggers> + <Root level="warn"> + <AppenderRef ref="Console"/> + </Root> + </Loggers> +</Configuration> diff --git a/opendc-experiments/opendc-experiments-greenifier/src/jmh/resources/topology.txt b/opendc-experiments/opendc-experiments-greenifier/src/jmh/resources/topology.txt new file mode 100644 index 00000000..6b347bff --- /dev/null +++ b/opendc-experiments/opendc-experiments-greenifier/src/jmh/resources/topology.txt @@ -0,0 +1,5 @@ +ClusterID;ClusterName;Cores;Speed;Memory;numberOfHosts;memoryCapacityPerHost;coreCountPerHost +A01;A01;32;3.2;2048;1;256;32 +B01;B01;48;2.93;1256;6;64;8 +C01;C01;32;3.2;2048;2;128;16 + diff --git a/opendc-experiments/opendc-experiments-greenifier/src/main/Python_scripts/.ipynb_checkpoints/OpenDCdemo-checkpoint.ipynb b/opendc-experiments/opendc-experiments-greenifier/src/main/Python_scripts/.ipynb_checkpoints/OpenDCdemo-checkpoint.ipynb new file mode 100644 index 00000000..15fc32f6 --- /dev/null +++ b/opendc-experiments/opendc-experiments-greenifier/src/main/Python_scripts/.ipynb_checkpoints/OpenDCdemo-checkpoint.ipynb @@ -0,0 +1,957 @@ +{ + "cells": [ + { + "cell_type": "code", + "execution_count": 5, + "id": "18170001", + "metadata": {}, + "outputs": [ + { + "data": { + "text/plain": [ + "['build.gradle.kts',\n", + " '.github',\n", + " 'opendc-web',\n", + " 'gradle.properties',\n", + " 'gradlew.bat',\n", + " 'docker-compose.yml',\n", + " '.gitignore',\n", + " 'resources',\n", + " '.dockerignore',\n", + " 'opendc-simulator',\n", + " '.gitattributes',\n", + " 'traces',\n", + " 'codecov.yml',\n", + " 'opendc-experiments',\n", + " '.editorconfig',\n", + " 'gradlew',\n", + " '.gradle',\n", + " 'site',\n", + " 'opendc-compute',\n", + " 'opendc-workflow',\n", + " 'output',\n", + " 'CONTRIBUTING.md',\n", + " 'opendc-trace',\n", + " 'LICENSE.txt',\n", + " 'docker-compose.prod.yml',\n", + " 'CITATION.cff',\n", + " '.git',\n", + " 'buildSrc',\n", + " 'build',\n", + " 'README.md',\n", + " 'opendc-common',\n", + " 'opendc-faas',\n", + " 'docker-compose.override.yml',\n", + " '.idea',\n", + " 'gradle',\n", + " 'settings.gradle.kts']" + ] + }, + "execution_count": 5, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "import numpy as np\n", + "import pandas as pd\n", + "import matplotlib.pyplot as plt\n", + "\n", + "from IPython.display import display, HTML\n", + "\n", + "base_folder = \"../../../..\"" + ] + }, + { + "cell_type": "markdown", + "id": "422f4d05", + "metadata": {}, + "source": [ + "## Topologies" + ] + }, + { + "cell_type": "code", + "execution_count": 2, + "id": "a2d05361", + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "Topology name: multi\n" + ] + }, + { + "ename": "FileNotFoundError", + "evalue": "[Errno 2] No such file or directory: '../resources/env/multi.txt'", + "output_type": "error", + "traceback": [ + "\u001b[0;31m---------------------------------------------------------------------------\u001b[0m", + "\u001b[0;31mFileNotFoundError\u001b[0m Traceback (most recent call last)", + "Cell \u001b[0;32mIn[2], line 6\u001b[0m\n\u001b[1;32m 3\u001b[0m df \u001b[38;5;241m=\u001b[39m pd\u001b[38;5;241m.\u001b[39mread_csv(\u001b[38;5;124mf\u001b[39m\u001b[38;5;124m\"\u001b[39m\u001b[38;5;124m../resources/env/\u001b[39m\u001b[38;5;132;01m{\u001b[39;00mtopology_name\u001b[38;5;132;01m}\u001b[39;00m\u001b[38;5;124m.txt\u001b[39m\u001b[38;5;124m\"\u001b[39m, delimiter\u001b[38;5;241m=\u001b[39m\u001b[38;5;124m\"\u001b[39m\u001b[38;5;124m;\u001b[39m\u001b[38;5;124m\"\u001b[39m)\n\u001b[1;32m 4\u001b[0m display(HTML(df\u001b[38;5;241m.\u001b[39mto_html()))\n\u001b[0;32m----> 6\u001b[0m \u001b[43mread_topology\u001b[49m\u001b[43m(\u001b[49m\u001b[38;5;124;43m\"\u001b[39;49m\u001b[38;5;124;43mmulti\u001b[39;49m\u001b[38;5;124;43m\"\u001b[39;49m\u001b[43m)\u001b[49m\n\u001b[1;32m 7\u001b[0m read_topology(\u001b[38;5;124m\"\u001b[39m\u001b[38;5;124msingle\u001b[39m\u001b[38;5;124m\"\u001b[39m)\n", + "Cell \u001b[0;32mIn[2], line 3\u001b[0m, in \u001b[0;36mread_topology\u001b[0;34m(topology_name)\u001b[0m\n\u001b[1;32m 1\u001b[0m \u001b[38;5;28;01mdef\u001b[39;00m \u001b[38;5;21mread_topology\u001b[39m(topology_name):\n\u001b[1;32m 2\u001b[0m \u001b[38;5;28mprint\u001b[39m(\u001b[38;5;124mf\u001b[39m\u001b[38;5;124m\"\u001b[39m\u001b[38;5;124mTopology name: \u001b[39m\u001b[38;5;132;01m{\u001b[39;00mtopology_name\u001b[38;5;132;01m}\u001b[39;00m\u001b[38;5;124m\"\u001b[39m)\n\u001b[0;32m----> 3\u001b[0m df \u001b[38;5;241m=\u001b[39m \u001b[43mpd\u001b[49m\u001b[38;5;241;43m.\u001b[39;49m\u001b[43mread_csv\u001b[49m\u001b[43m(\u001b[49m\u001b[38;5;124;43mf\u001b[39;49m\u001b[38;5;124;43m\"\u001b[39;49m\u001b[38;5;124;43m../resources/env/\u001b[39;49m\u001b[38;5;132;43;01m{\u001b[39;49;00m\u001b[43mtopology_name\u001b[49m\u001b[38;5;132;43;01m}\u001b[39;49;00m\u001b[38;5;124;43m.txt\u001b[39;49m\u001b[38;5;124;43m\"\u001b[39;49m\u001b[43m,\u001b[49m\u001b[43m \u001b[49m\u001b[43mdelimiter\u001b[49m\u001b[38;5;241;43m=\u001b[39;49m\u001b[38;5;124;43m\"\u001b[39;49m\u001b[38;5;124;43m;\u001b[39;49m\u001b[38;5;124;43m\"\u001b[39;49m\u001b[43m)\u001b[49m\n\u001b[1;32m 4\u001b[0m display(HTML(df\u001b[38;5;241m.\u001b[39mto_html()))\n", + "File \u001b[0;32m~/.local/lib/python3.10/site-packages/pandas/util/_decorators.py:211\u001b[0m, in \u001b[0;36mdeprecate_kwarg.<locals>._deprecate_kwarg.<locals>.wrapper\u001b[0;34m(*args, **kwargs)\u001b[0m\n\u001b[1;32m 209\u001b[0m \u001b[38;5;28;01melse\u001b[39;00m:\n\u001b[1;32m 210\u001b[0m kwargs[new_arg_name] \u001b[38;5;241m=\u001b[39m new_arg_value\n\u001b[0;32m--> 211\u001b[0m \u001b[38;5;28;01mreturn\u001b[39;00m \u001b[43mfunc\u001b[49m\u001b[43m(\u001b[49m\u001b[38;5;241;43m*\u001b[39;49m\u001b[43margs\u001b[49m\u001b[43m,\u001b[49m\u001b[43m \u001b[49m\u001b[38;5;241;43m*\u001b[39;49m\u001b[38;5;241;43m*\u001b[39;49m\u001b[43mkwargs\u001b[49m\u001b[43m)\u001b[49m\n", + "File \u001b[0;32m~/.local/lib/python3.10/site-packages/pandas/util/_decorators.py:317\u001b[0m, in \u001b[0;36mdeprecate_nonkeyword_arguments.<locals>.decorate.<locals>.wrapper\u001b[0;34m(*args, **kwargs)\u001b[0m\n\u001b[1;32m 311\u001b[0m \u001b[38;5;28;01mif\u001b[39;00m \u001b[38;5;28mlen\u001b[39m(args) \u001b[38;5;241m>\u001b[39m num_allow_args:\n\u001b[1;32m 312\u001b[0m warnings\u001b[38;5;241m.\u001b[39mwarn(\n\u001b[1;32m 313\u001b[0m msg\u001b[38;5;241m.\u001b[39mformat(arguments\u001b[38;5;241m=\u001b[39marguments),\n\u001b[1;32m 314\u001b[0m \u001b[38;5;167;01mFutureWarning\u001b[39;00m,\n\u001b[1;32m 315\u001b[0m stacklevel\u001b[38;5;241m=\u001b[39mfind_stack_level(inspect\u001b[38;5;241m.\u001b[39mcurrentframe()),\n\u001b[1;32m 316\u001b[0m )\n\u001b[0;32m--> 317\u001b[0m \u001b[38;5;28;01mreturn\u001b[39;00m \u001b[43mfunc\u001b[49m\u001b[43m(\u001b[49m\u001b[38;5;241;43m*\u001b[39;49m\u001b[43margs\u001b[49m\u001b[43m,\u001b[49m\u001b[43m \u001b[49m\u001b[38;5;241;43m*\u001b[39;49m\u001b[38;5;241;43m*\u001b[39;49m\u001b[43mkwargs\u001b[49m\u001b[43m)\u001b[49m\n", + "File \u001b[0;32m~/.local/lib/python3.10/site-packages/pandas/io/parsers/readers.py:950\u001b[0m, in \u001b[0;36mread_csv\u001b[0;34m(filepath_or_buffer, sep, delimiter, header, names, index_col, usecols, squeeze, prefix, mangle_dupe_cols, dtype, engine, converters, true_values, false_values, skipinitialspace, skiprows, skipfooter, nrows, na_values, keep_default_na, na_filter, verbose, skip_blank_lines, parse_dates, infer_datetime_format, keep_date_col, date_parser, dayfirst, cache_dates, iterator, chunksize, compression, thousands, decimal, lineterminator, quotechar, quoting, doublequote, escapechar, comment, encoding, encoding_errors, dialect, error_bad_lines, warn_bad_lines, on_bad_lines, delim_whitespace, low_memory, memory_map, float_precision, storage_options)\u001b[0m\n\u001b[1;32m 935\u001b[0m kwds_defaults \u001b[38;5;241m=\u001b[39m _refine_defaults_read(\n\u001b[1;32m 936\u001b[0m dialect,\n\u001b[1;32m 937\u001b[0m delimiter,\n\u001b[0;32m (...)\u001b[0m\n\u001b[1;32m 946\u001b[0m defaults\u001b[38;5;241m=\u001b[39m{\u001b[38;5;124m\"\u001b[39m\u001b[38;5;124mdelimiter\u001b[39m\u001b[38;5;124m\"\u001b[39m: \u001b[38;5;124m\"\u001b[39m\u001b[38;5;124m,\u001b[39m\u001b[38;5;124m\"\u001b[39m},\n\u001b[1;32m 947\u001b[0m )\n\u001b[1;32m 948\u001b[0m kwds\u001b[38;5;241m.\u001b[39mupdate(kwds_defaults)\n\u001b[0;32m--> 950\u001b[0m \u001b[38;5;28;01mreturn\u001b[39;00m \u001b[43m_read\u001b[49m\u001b[43m(\u001b[49m\u001b[43mfilepath_or_buffer\u001b[49m\u001b[43m,\u001b[49m\u001b[43m \u001b[49m\u001b[43mkwds\u001b[49m\u001b[43m)\u001b[49m\n", + "File \u001b[0;32m~/.local/lib/python3.10/site-packages/pandas/io/parsers/readers.py:605\u001b[0m, in \u001b[0;36m_read\u001b[0;34m(filepath_or_buffer, kwds)\u001b[0m\n\u001b[1;32m 602\u001b[0m _validate_names(kwds\u001b[38;5;241m.\u001b[39mget(\u001b[38;5;124m\"\u001b[39m\u001b[38;5;124mnames\u001b[39m\u001b[38;5;124m\"\u001b[39m, \u001b[38;5;28;01mNone\u001b[39;00m))\n\u001b[1;32m 604\u001b[0m \u001b[38;5;66;03m# Create the parser.\u001b[39;00m\n\u001b[0;32m--> 605\u001b[0m parser \u001b[38;5;241m=\u001b[39m \u001b[43mTextFileReader\u001b[49m\u001b[43m(\u001b[49m\u001b[43mfilepath_or_buffer\u001b[49m\u001b[43m,\u001b[49m\u001b[43m \u001b[49m\u001b[38;5;241;43m*\u001b[39;49m\u001b[38;5;241;43m*\u001b[39;49m\u001b[43mkwds\u001b[49m\u001b[43m)\u001b[49m\n\u001b[1;32m 607\u001b[0m \u001b[38;5;28;01mif\u001b[39;00m chunksize \u001b[38;5;129;01mor\u001b[39;00m iterator:\n\u001b[1;32m 608\u001b[0m \u001b[38;5;28;01mreturn\u001b[39;00m parser\n", + "File \u001b[0;32m~/.local/lib/python3.10/site-packages/pandas/io/parsers/readers.py:1442\u001b[0m, in \u001b[0;36mTextFileReader.__init__\u001b[0;34m(self, f, engine, **kwds)\u001b[0m\n\u001b[1;32m 1439\u001b[0m \u001b[38;5;28mself\u001b[39m\u001b[38;5;241m.\u001b[39moptions[\u001b[38;5;124m\"\u001b[39m\u001b[38;5;124mhas_index_names\u001b[39m\u001b[38;5;124m\"\u001b[39m] \u001b[38;5;241m=\u001b[39m kwds[\u001b[38;5;124m\"\u001b[39m\u001b[38;5;124mhas_index_names\u001b[39m\u001b[38;5;124m\"\u001b[39m]\n\u001b[1;32m 1441\u001b[0m \u001b[38;5;28mself\u001b[39m\u001b[38;5;241m.\u001b[39mhandles: IOHandles \u001b[38;5;241m|\u001b[39m \u001b[38;5;28;01mNone\u001b[39;00m \u001b[38;5;241m=\u001b[39m \u001b[38;5;28;01mNone\u001b[39;00m\n\u001b[0;32m-> 1442\u001b[0m \u001b[38;5;28mself\u001b[39m\u001b[38;5;241m.\u001b[39m_engine \u001b[38;5;241m=\u001b[39m \u001b[38;5;28;43mself\u001b[39;49m\u001b[38;5;241;43m.\u001b[39;49m\u001b[43m_make_engine\u001b[49m\u001b[43m(\u001b[49m\u001b[43mf\u001b[49m\u001b[43m,\u001b[49m\u001b[43m \u001b[49m\u001b[38;5;28;43mself\u001b[39;49m\u001b[38;5;241;43m.\u001b[39;49m\u001b[43mengine\u001b[49m\u001b[43m)\u001b[49m\n", + "File \u001b[0;32m~/.local/lib/python3.10/site-packages/pandas/io/parsers/readers.py:1729\u001b[0m, in \u001b[0;36mTextFileReader._make_engine\u001b[0;34m(self, f, engine)\u001b[0m\n\u001b[1;32m 1727\u001b[0m is_text \u001b[38;5;241m=\u001b[39m \u001b[38;5;28;01mFalse\u001b[39;00m\n\u001b[1;32m 1728\u001b[0m mode \u001b[38;5;241m=\u001b[39m \u001b[38;5;124m\"\u001b[39m\u001b[38;5;124mrb\u001b[39m\u001b[38;5;124m\"\u001b[39m\n\u001b[0;32m-> 1729\u001b[0m \u001b[38;5;28mself\u001b[39m\u001b[38;5;241m.\u001b[39mhandles \u001b[38;5;241m=\u001b[39m \u001b[43mget_handle\u001b[49m\u001b[43m(\u001b[49m\n\u001b[1;32m 1730\u001b[0m \u001b[43m \u001b[49m\u001b[43mf\u001b[49m\u001b[43m,\u001b[49m\n\u001b[1;32m 1731\u001b[0m \u001b[43m \u001b[49m\u001b[43mmode\u001b[49m\u001b[43m,\u001b[49m\n\u001b[1;32m 1732\u001b[0m \u001b[43m \u001b[49m\u001b[43mencoding\u001b[49m\u001b[38;5;241;43m=\u001b[39;49m\u001b[38;5;28;43mself\u001b[39;49m\u001b[38;5;241;43m.\u001b[39;49m\u001b[43moptions\u001b[49m\u001b[38;5;241;43m.\u001b[39;49m\u001b[43mget\u001b[49m\u001b[43m(\u001b[49m\u001b[38;5;124;43m\"\u001b[39;49m\u001b[38;5;124;43mencoding\u001b[39;49m\u001b[38;5;124;43m\"\u001b[39;49m\u001b[43m,\u001b[49m\u001b[43m \u001b[49m\u001b[38;5;28;43;01mNone\u001b[39;49;00m\u001b[43m)\u001b[49m\u001b[43m,\u001b[49m\n\u001b[1;32m 1733\u001b[0m \u001b[43m \u001b[49m\u001b[43mcompression\u001b[49m\u001b[38;5;241;43m=\u001b[39;49m\u001b[38;5;28;43mself\u001b[39;49m\u001b[38;5;241;43m.\u001b[39;49m\u001b[43moptions\u001b[49m\u001b[38;5;241;43m.\u001b[39;49m\u001b[43mget\u001b[49m\u001b[43m(\u001b[49m\u001b[38;5;124;43m\"\u001b[39;49m\u001b[38;5;124;43mcompression\u001b[39;49m\u001b[38;5;124;43m\"\u001b[39;49m\u001b[43m,\u001b[49m\u001b[43m \u001b[49m\u001b[38;5;28;43;01mNone\u001b[39;49;00m\u001b[43m)\u001b[49m\u001b[43m,\u001b[49m\n\u001b[1;32m 1734\u001b[0m \u001b[43m \u001b[49m\u001b[43mmemory_map\u001b[49m\u001b[38;5;241;43m=\u001b[39;49m\u001b[38;5;28;43mself\u001b[39;49m\u001b[38;5;241;43m.\u001b[39;49m\u001b[43moptions\u001b[49m\u001b[38;5;241;43m.\u001b[39;49m\u001b[43mget\u001b[49m\u001b[43m(\u001b[49m\u001b[38;5;124;43m\"\u001b[39;49m\u001b[38;5;124;43mmemory_map\u001b[39;49m\u001b[38;5;124;43m\"\u001b[39;49m\u001b[43m,\u001b[49m\u001b[43m \u001b[49m\u001b[38;5;28;43;01mFalse\u001b[39;49;00m\u001b[43m)\u001b[49m\u001b[43m,\u001b[49m\n\u001b[1;32m 1735\u001b[0m \u001b[43m \u001b[49m\u001b[43mis_text\u001b[49m\u001b[38;5;241;43m=\u001b[39;49m\u001b[43mis_text\u001b[49m\u001b[43m,\u001b[49m\n\u001b[1;32m 1736\u001b[0m \u001b[43m \u001b[49m\u001b[43merrors\u001b[49m\u001b[38;5;241;43m=\u001b[39;49m\u001b[38;5;28;43mself\u001b[39;49m\u001b[38;5;241;43m.\u001b[39;49m\u001b[43moptions\u001b[49m\u001b[38;5;241;43m.\u001b[39;49m\u001b[43mget\u001b[49m\u001b[43m(\u001b[49m\u001b[38;5;124;43m\"\u001b[39;49m\u001b[38;5;124;43mencoding_errors\u001b[39;49m\u001b[38;5;124;43m\"\u001b[39;49m\u001b[43m,\u001b[49m\u001b[43m \u001b[49m\u001b[38;5;124;43m\"\u001b[39;49m\u001b[38;5;124;43mstrict\u001b[39;49m\u001b[38;5;124;43m\"\u001b[39;49m\u001b[43m)\u001b[49m\u001b[43m,\u001b[49m\n\u001b[1;32m 1737\u001b[0m \u001b[43m \u001b[49m\u001b[43mstorage_options\u001b[49m\u001b[38;5;241;43m=\u001b[39;49m\u001b[38;5;28;43mself\u001b[39;49m\u001b[38;5;241;43m.\u001b[39;49m\u001b[43moptions\u001b[49m\u001b[38;5;241;43m.\u001b[39;49m\u001b[43mget\u001b[49m\u001b[43m(\u001b[49m\u001b[38;5;124;43m\"\u001b[39;49m\u001b[38;5;124;43mstorage_options\u001b[39;49m\u001b[38;5;124;43m\"\u001b[39;49m\u001b[43m,\u001b[49m\u001b[43m \u001b[49m\u001b[38;5;28;43;01mNone\u001b[39;49;00m\u001b[43m)\u001b[49m\u001b[43m,\u001b[49m\n\u001b[1;32m 1738\u001b[0m \u001b[43m\u001b[49m\u001b[43m)\u001b[49m\n\u001b[1;32m 1739\u001b[0m \u001b[38;5;28;01massert\u001b[39;00m \u001b[38;5;28mself\u001b[39m\u001b[38;5;241m.\u001b[39mhandles \u001b[38;5;129;01mis\u001b[39;00m \u001b[38;5;129;01mnot\u001b[39;00m \u001b[38;5;28;01mNone\u001b[39;00m\n\u001b[1;32m 1740\u001b[0m f \u001b[38;5;241m=\u001b[39m \u001b[38;5;28mself\u001b[39m\u001b[38;5;241m.\u001b[39mhandles\u001b[38;5;241m.\u001b[39mhandle\n", + "File \u001b[0;32m~/.local/lib/python3.10/site-packages/pandas/io/common.py:857\u001b[0m, in \u001b[0;36mget_handle\u001b[0;34m(path_or_buf, mode, encoding, compression, memory_map, is_text, errors, storage_options)\u001b[0m\n\u001b[1;32m 852\u001b[0m \u001b[38;5;28;01melif\u001b[39;00m \u001b[38;5;28misinstance\u001b[39m(handle, \u001b[38;5;28mstr\u001b[39m):\n\u001b[1;32m 853\u001b[0m \u001b[38;5;66;03m# Check whether the filename is to be opened in binary mode.\u001b[39;00m\n\u001b[1;32m 854\u001b[0m \u001b[38;5;66;03m# Binary mode does not support 'encoding' and 'newline'.\u001b[39;00m\n\u001b[1;32m 855\u001b[0m \u001b[38;5;28;01mif\u001b[39;00m ioargs\u001b[38;5;241m.\u001b[39mencoding \u001b[38;5;129;01mand\u001b[39;00m \u001b[38;5;124m\"\u001b[39m\u001b[38;5;124mb\u001b[39m\u001b[38;5;124m\"\u001b[39m \u001b[38;5;129;01mnot\u001b[39;00m \u001b[38;5;129;01min\u001b[39;00m ioargs\u001b[38;5;241m.\u001b[39mmode:\n\u001b[1;32m 856\u001b[0m \u001b[38;5;66;03m# Encoding\u001b[39;00m\n\u001b[0;32m--> 857\u001b[0m handle \u001b[38;5;241m=\u001b[39m \u001b[38;5;28;43mopen\u001b[39;49m\u001b[43m(\u001b[49m\n\u001b[1;32m 858\u001b[0m \u001b[43m \u001b[49m\u001b[43mhandle\u001b[49m\u001b[43m,\u001b[49m\n\u001b[1;32m 859\u001b[0m \u001b[43m \u001b[49m\u001b[43mioargs\u001b[49m\u001b[38;5;241;43m.\u001b[39;49m\u001b[43mmode\u001b[49m\u001b[43m,\u001b[49m\n\u001b[1;32m 860\u001b[0m \u001b[43m \u001b[49m\u001b[43mencoding\u001b[49m\u001b[38;5;241;43m=\u001b[39;49m\u001b[43mioargs\u001b[49m\u001b[38;5;241;43m.\u001b[39;49m\u001b[43mencoding\u001b[49m\u001b[43m,\u001b[49m\n\u001b[1;32m 861\u001b[0m \u001b[43m \u001b[49m\u001b[43merrors\u001b[49m\u001b[38;5;241;43m=\u001b[39;49m\u001b[43merrors\u001b[49m\u001b[43m,\u001b[49m\n\u001b[1;32m 862\u001b[0m \u001b[43m \u001b[49m\u001b[43mnewline\u001b[49m\u001b[38;5;241;43m=\u001b[39;49m\u001b[38;5;124;43m\"\u001b[39;49m\u001b[38;5;124;43m\"\u001b[39;49m\u001b[43m,\u001b[49m\n\u001b[1;32m 863\u001b[0m \u001b[43m \u001b[49m\u001b[43m)\u001b[49m\n\u001b[1;32m 864\u001b[0m \u001b[38;5;28;01melse\u001b[39;00m:\n\u001b[1;32m 865\u001b[0m \u001b[38;5;66;03m# Binary mode\u001b[39;00m\n\u001b[1;32m 866\u001b[0m handle \u001b[38;5;241m=\u001b[39m \u001b[38;5;28mopen\u001b[39m(handle, ioargs\u001b[38;5;241m.\u001b[39mmode)\n", + "\u001b[0;31mFileNotFoundError\u001b[0m: [Errno 2] No such file or directory: '../resources/env/multi.txt'" + ] + } + ], + "source": [ + "def read_topology(topology_name):\n", + " print(f\"Topology name: {topology_name}\")\n", + " df = pd.read_csv(f\"../resources/env/{topology_name}.txt\", delimiter=\";\")\n", + " display(HTML(df.to_html()))\n", + " \n", + "read_topology(\"multi\")\n", + "read_topology(\"single\")" + ] + }, + { + "cell_type": "markdown", + "id": "8f4fe54d", + "metadata": {}, + "source": [ + "## Traces" + ] + }, + { + "cell_type": "code", + "execution_count": 4, + "id": "fd17d88a", + "metadata": {}, + "outputs": [ + { + "data": { + "text/html": [ + "<div>\n", + "<style scoped>\n", + " .dataframe tbody tr th:only-of-type {\n", + " vertical-align: middle;\n", + " }\n", + "\n", + " .dataframe tbody tr th {\n", + " vertical-align: top;\n", + " }\n", + "\n", + " .dataframe thead th {\n", + " text-align: right;\n", + " }\n", + "</style>\n", + "<table border=\"1\" class=\"dataframe\">\n", + " <thead>\n", + " <tr style=\"text-align: right;\">\n", + " <th></th>\n", + " <th>id</th>\n", + " <th>timestamp</th>\n", + " <th>duration</th>\n", + " <th>cpu_count</th>\n", + " <th>cpu_usage</th>\n", + " </tr>\n", + " </thead>\n", + " <tbody>\n", + " <tr>\n", + " <th>0</th>\n", + " <td>1019</td>\n", + " <td>2013-08-12 13:40:46+00:00</td>\n", + " <td>300000</td>\n", + " <td>1</td>\n", + " <td>0.000000</td>\n", + " </tr>\n", + " <tr>\n", + " <th>1</th>\n", + " <td>1019</td>\n", + " <td>2013-08-12 13:45:46+00:00</td>\n", + " <td>300000</td>\n", + " <td>1</td>\n", + " <td>11.703998</td>\n", + " </tr>\n", + " <tr>\n", + " <th>2</th>\n", + " <td>1019</td>\n", + " <td>2013-08-12 13:55:46+00:00</td>\n", + " <td>600000</td>\n", + " <td>1</td>\n", + " <td>0.000000</td>\n", + " </tr>\n", + " <tr>\n", + " <th>3</th>\n", + " <td>1019</td>\n", + " <td>2013-08-12 14:00:46+00:00</td>\n", + " <td>300000</td>\n", + " <td>1</td>\n", + " <td>11.703998</td>\n", + " </tr>\n", + " <tr>\n", + " <th>4</th>\n", + " <td>1019</td>\n", + " <td>2013-08-12 14:15:46+00:00</td>\n", + " <td>900000</td>\n", + " <td>1</td>\n", + " <td>0.000000</td>\n", + " </tr>\n", + " </tbody>\n", + "</table>\n", + "</div>" + ], + "text/plain": [ + " id timestamp duration cpu_count cpu_usage\n", + "0 1019 2013-08-12 13:40:46+00:00 300000 1 0.000000\n", + "1 1019 2013-08-12 13:45:46+00:00 300000 1 11.703998\n", + "2 1019 2013-08-12 13:55:46+00:00 600000 1 0.000000\n", + "3 1019 2013-08-12 14:00:46+00:00 300000 1 11.703998\n", + "4 1019 2013-08-12 14:15:46+00:00 900000 1 0.000000" + ] + }, + "execution_count": 4, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "trace = pd.read_parquet(f\"resources/bitbrains-small/trace/trace.parquet\")\n", + "trace.head(5)" + ] + }, + { + "cell_type": "code", + "execution_count": 5, + "id": "346f097f", + "metadata": { + "scrolled": true + }, + "outputs": [ + { + "data": { + "text/html": [ + "<div>\n", + "<style scoped>\n", + " .dataframe tbody tr th:only-of-type {\n", + " vertical-align: middle;\n", + " }\n", + "\n", + " .dataframe tbody tr th {\n", + " vertical-align: top;\n", + " }\n", + "\n", + " .dataframe thead th {\n", + " text-align: right;\n", + " }\n", + "</style>\n", + "<table border=\"1\" class=\"dataframe\">\n", + " <thead>\n", + " <tr style=\"text-align: right;\">\n", + " <th></th>\n", + " <th>id</th>\n", + " <th>start_time</th>\n", + " <th>stop_time</th>\n", + " <th>cpu_count</th>\n", + " <th>cpu_capacity</th>\n", + " <th>mem_capacity</th>\n", + " </tr>\n", + " </thead>\n", + " <tbody>\n", + " <tr>\n", + " <th>0</th>\n", + " <td>1019</td>\n", + " <td>2013-08-12 13:35:46+00:00</td>\n", + " <td>2013-09-11 13:39:58+00:00</td>\n", + " <td>1</td>\n", + " <td>2926.000135</td>\n", + " <td>181352</td>\n", + " </tr>\n", + " <tr>\n", + " <th>1</th>\n", + " <td>1023</td>\n", + " <td>2013-08-12 13:35:46+00:00</td>\n", + " <td>2013-09-11 13:39:58+00:00</td>\n", + " <td>1</td>\n", + " <td>2925.999560</td>\n", + " <td>260096</td>\n", + " </tr>\n", + " <tr>\n", + " <th>2</th>\n", + " <td>1026</td>\n", + " <td>2013-08-12 13:35:46+00:00</td>\n", + " <td>2013-09-11 13:39:58+00:00</td>\n", + " <td>1</td>\n", + " <td>2925.999717</td>\n", + " <td>249972</td>\n", + " </tr>\n", + " <tr>\n", + " <th>3</th>\n", + " <td>1052</td>\n", + " <td>2013-08-29 14:38:12+00:00</td>\n", + " <td>2013-09-05 07:09:07+00:00</td>\n", + " <td>1</td>\n", + " <td>2926.000107</td>\n", + " <td>131245</td>\n", + " </tr>\n", + " <tr>\n", + " <th>4</th>\n", + " <td>1073</td>\n", + " <td>2013-08-21 11:07:12+00:00</td>\n", + " <td>2013-09-11 13:39:58+00:00</td>\n", + " <td>1</td>\n", + " <td>2599.999649</td>\n", + " <td>179306</td>\n", + " </tr>\n", + " </tbody>\n", + "</table>\n", + "</div>" + ], + "text/plain": [ + " id start_time stop_time cpu_count \\\n", + "0 1019 2013-08-12 13:35:46+00:00 2013-09-11 13:39:58+00:00 1 \n", + "1 1023 2013-08-12 13:35:46+00:00 2013-09-11 13:39:58+00:00 1 \n", + "2 1026 2013-08-12 13:35:46+00:00 2013-09-11 13:39:58+00:00 1 \n", + "3 1052 2013-08-29 14:38:12+00:00 2013-09-05 07:09:07+00:00 1 \n", + "4 1073 2013-08-21 11:07:12+00:00 2013-09-11 13:39:58+00:00 1 \n", + "\n", + " cpu_capacity mem_capacity \n", + "0 2926.000135 181352 \n", + "1 2925.999560 260096 \n", + "2 2925.999717 249972 \n", + "3 2926.000107 131245 \n", + "4 2599.999649 179306 " + ] + }, + "execution_count": 5, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "meta = pd.read_parquet(f\"resources/bitbrains-small/trace/meta.parquet\")\n", + "meta.head(5)" + ] + }, + { + "cell_type": "markdown", + "id": "13bf9fdb", + "metadata": {}, + "source": [ + "# Lets run this in OpenDC!" + ] + }, + { + "cell_type": "markdown", + "id": "c9766446", + "metadata": {}, + "source": [ + "## Resulting Files" + ] + }, + { + "cell_type": "code", + "execution_count": 23, + "id": "0d400ffd", + "metadata": {}, + "outputs": [], + "source": [ + "output_folder = \"output\"\n", + "workload = \"workload=bitbrains-small\"\n", + "seed = \"seed=0\"\n", + "\n", + "df_host_multi = pd.read_parquet(f\"{output_folder}/host/topology=multi/{workload}/{seed}/data.parquet\")\n", + "df_host_single = pd.read_parquet(f\"{output_folder}/host/topology=single/{workload}/{seed}/data.parquet\")\n", + "\n", + "df_server_multi = pd.read_parquet(f\"{output_folder}/server/topology=multi/{workload}/{seed}/data.parquet\")\n", + "df_server_single = pd.read_parquet(f\"{output_folder}/server/topology=single/{workload}/{seed}/data.parquet\")\n", + "\n", + "df_service_multi = pd.read_parquet(f\"{output_folder}/service/topology=multi/{workload}/{seed}/data.parquet\")\n", + "df_service_single = pd.read_parquet(f\"{output_folder}/service/topology=single/{workload}/{seed}/data.parquet\")" + ] + }, + { + "cell_type": "markdown", + "id": "6d494d6e", + "metadata": {}, + "source": [ + "### Host" + ] + }, + { + "cell_type": "code", + "execution_count": 24, + "id": "48a1e1a6", + "metadata": {}, + "outputs": [ + { + "data": { + "text/plain": [ + "['timestamp',\n", + " 'host_id',\n", + " 'cpu_count',\n", + " 'mem_capacity',\n", + " 'guests_terminated',\n", + " 'guests_running',\n", + " 'guests_error',\n", + " 'guests_invalid',\n", + " 'cpu_limit',\n", + " 'cpu_usage',\n", + " 'cpu_demand',\n", + " 'cpu_utilization',\n", + " 'cpu_time_active',\n", + " 'cpu_time_idle',\n", + " 'cpu_time_steal',\n", + " 'cpu_time_lost',\n", + " 'power_total',\n", + " 'uptime',\n", + " 'downtime',\n", + " 'boot_time']" + ] + }, + "metadata": {}, + "output_type": "display_data" + }, + { + "name": "stdout", + "output_type": "stream", + "text": [ + "number of measurements: 77778\n" + ] + } + ], + "source": [ + "display(list(df_host_multi.columns))\n", + "print(f\"number of measurements: {len(df_host_multi)}\")" + ] + }, + { + "cell_type": "markdown", + "id": "9eb9be2c", + "metadata": {}, + "source": [ + "### Server" + ] + }, + { + "cell_type": "code", + "execution_count": 25, + "id": "57a2b148", + "metadata": {}, + "outputs": [ + { + "data": { + "text/plain": [ + "['timestamp',\n", + " 'server_id',\n", + " 'host_id',\n", + " 'uptime',\n", + " 'downtime',\n", + " 'provision_time',\n", + " 'boot_time',\n", + " 'cpu_count',\n", + " 'cpu_limit',\n", + " 'cpu_time_active',\n", + " 'cpu_time_idle',\n", + " 'cpu_time_steal',\n", + " 'cpu_time_lost',\n", + " 'mem_limit']" + ] + }, + "metadata": {}, + "output_type": "display_data" + }, + { + "name": "stdout", + "output_type": "stream", + "text": [ + "number of measurements: 408040\n" + ] + } + ], + "source": [ + "display(list(df_server_multi.columns))\n", + "print(f\"number of measurements: {len(df_server_multi)}\")" + ] + }, + { + "cell_type": "markdown", + "id": "fbe5f439", + "metadata": {}, + "source": [ + "### Service" + ] + }, + { + "cell_type": "code", + "execution_count": 26, + "id": "9ef468ed", + "metadata": { + "scrolled": false + }, + "outputs": [ + { + "data": { + "text/plain": [ + "['timestamp',\n", + " 'hosts_up',\n", + " 'hosts_down',\n", + " 'servers_pending',\n", + " 'servers_active',\n", + " 'attempts_success',\n", + " 'attempts_failure',\n", + " 'attempts_error']" + ] + }, + "metadata": {}, + "output_type": "display_data" + }, + { + "name": "stdout", + "output_type": "stream", + "text": [ + "number of measurements: 8642\n" + ] + } + ], + "source": [ + "display(list(df_service_single.columns))\n", + "print(f\"number of measurements: {len(df_host_single)}\")" + ] + }, + { + "cell_type": "markdown", + "id": "09d31c91", + "metadata": {}, + "source": [ + "## Power Usage" + ] + }, + { + "cell_type": "code", + "execution_count": 27, + "id": "82f0a24a", + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "big topology: 3400876.9739568997\n", + "small topology: 60000.000858306885\n" + ] + } + ], + "source": [ + "print(f\"big topology: {df_host_multi.power_total.sum()}\")\n", + "print(f\"small topology: {df_host_single.power_total.sum()}\")" + ] + }, + { + "cell_type": "markdown", + "id": "7ab3357d", + "metadata": {}, + "source": [ + "## CPU usage" + ] + }, + { + "cell_type": "code", + "execution_count": 32, + "id": "e94db3a6", + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "big topology: 2.352397194967523e-05\n", + "small topology: 0.00011571395510298542\n" + ] + } + ], + "source": [ + "print(f\"big topology: {df_host_multi.cpu_utilization.mean()}\")\n", + "print(f\"small topology: {df_host_single.cpu_utilization.mean()}\")" + ] + }, + { + "cell_type": "markdown", + "id": "e000a260", + "metadata": {}, + "source": [ + "## CPU utilization" + ] + }, + { + "cell_type": "code", + "execution_count": 29, + "id": "8d7daa45", + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "big topology: 2.352397194967523e-05\n", + "small topology: 0.00011571395510298542\n" + ] + } + ], + "source": [ + "print(f\"big topology: {df_host_multi.cpu_utilization.mean()}\")\n", + "print(f\"small topology: {df_host_single.cpu_utilization.mean()}\")" + ] + }, + { + "cell_type": "markdown", + "id": "ad97741c", + "metadata": {}, + "source": [ + "## Plotting Results" + ] + }, + { + "cell_type": "code", + "execution_count": 13, + "id": "5df8f9aa", + "metadata": {}, + "outputs": [ + { + "data": { + "image/png": "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OnjwoL3N39/fHmwkKTw83H4KDIBrWLkBIElq27atLBaLDh06VKn+Tz/9tEaOHKl69eopNDRUFovF/llgYKC+++67cmN++eUXeXt7V3jaS5K8vC79/9Zvg9ZvT+G4U1ZWlu6991498sgjGj16tL29oKBAsbGxio2N1TvvvKPGjRsrIyNDsbGx1XLazcfHx+G9xWJxOmgCcMTKDQBJl+70iY2NVVJSkgoKCsp9/vvbnoODg9WmTRuFhYU5BBtJat++vb755hsVFhY6tO/fv18tW7Ys94V+WePGjSXJ4YLa3z+bxmq1qrS0tLKHVaGLFy9q0KBB6tChgxYtWuTw2aFDh/Tjjz9q7ty56tu3rzp06FBuJcVqtUrSVevo2LGjSkpKtHv3bnvbjz/+qMOHD6tTp05Vqh/A1RFuANglJSWptLRUPXv21IcffqijR4/q4MGDWrJkiaKjoyu9nWHDhslisSguLk779u3TsWPHtGLFCi1evFhPPvnkFce1adNGNptNzz33nI4ePaqNGzdq4cKFDn0iIiJ0/vx5JScn69y5c7pw4YLTx/noo4/q9OnTWrJkiXJycpSVlaWsrCwVFRWpefPmslqtevXVV3XixAlt2LCh3LNrWrRoIYvFoo8//lg5OTk6f/58uX20bdtWgwYN0pgxY7Rjxw599dVXeuihh9S0aVMNGjTI6ZoBVB7hBoBdq1attH//fvXr109PPvmkOnfurP79+ys5OVmvv/56pbdTv359ff755youLta9996ryMhILVmyRIsWLdKjjz56xXE+Pj567733dOjQId10002aN2+eXnjhBYc+vXr10mOPPaYhQ4aocePGmj9/vtPH+b//+7/KzMxUp06dFB4ebn/t2rVLjRs31qpVq7R27Vp16tRJc+fO1UsvveQwvmnTppo1a5amTJmi0NBQTZgwocL9rFy5Ut26ddO//du/KTo6WoZhaNOmTVdcuQLgHhaDk7sAAMBEWLkBAACmQrgBAACmQrgBAACmQrgBAACmQrgBAACmQrgBAACmQrgBAACmQrgBAACmQrgBAACmQrgBAACmQrgBAACm8v8BjCGIOvW4gUwAAAAASUVORK5CYII=", + "text/plain": [ + "<Figure size 640x480 with 1 Axes>" + ] + }, + "metadata": {}, + "output_type": "display_data" + } + ], + "source": [ + "data = df_host_multi.cpu_utilization\n", + "plt.hist(data, weights=np.ones_like(data) / len(data),\n", + " alpha=0.7, label=\"big\", bins=30)\n", + "\n", + "\n", + "data = df_host_single.cpu_utilization\n", + "plt.hist(data, weights=np.ones_like(data) / len(data),\n", + " alpha=0.7, label=\"small\", bins=30)\n", + "\n", + "plt.xlabel(\"CPU utilization\")\n", + "plt.ylabel(\"Frequency\")\n", + "plt.legend()\n", + "plt.show()" + ] + }, + { + "cell_type": "code", + "execution_count": 14, + "id": "520e42a4", + "metadata": {}, + "outputs": [ + { + "data": { + "text/plain": [ + "0.0 8641\n", + "1.0 1\n", + "Name: cpu_utilization, dtype: int64" + ] + }, + "execution_count": 14, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "df_host_single.cpu_utilization.value_counts()" + ] + }, + { + "cell_type": "code", + "execution_count": 22, + "id": "a8c35267", + "metadata": {}, + "outputs": [ + { + "data": { + "text/plain": [ + "array([b'\\xf8\\x8b\\xb8\\xa8rL\\x81\\xec\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x02',\n", + " b'\\x1b9\\x89jQ\\xa8t\\x9b\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x03',\n", + " b'\\xc5\\x84\\x13:\\xc9\\x16\\xab<\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00',\n", + " b'S\\xcb\\x9f\\x0ct~\\xa2\\xea\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x04',\n", + " b'\\xe2 \\xa89{\\x1d\\xcd\\xaf\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00',\n", + " b'\\x06\\xc4]\\x18\\x80\\tEO\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x01',\n", + " b',\\x82\\x9a\\xbe\\x1fE2\\xe1\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x05',\n", + " b'>\\xe5x\\x90A\\xc9\\x8a\\xc3\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x01',\n", + " b'nx\\x9ej\\xa1\\xb9e\\xf4\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00'],\n", + " dtype=object)" + ] + }, + "execution_count": 22, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "df_host_multi.host_id.unique()" + ] + }, + { + "cell_type": "code", + "execution_count": 30, + "id": "68546b09", + "metadata": {}, + "outputs": [ + { + "data": { + "text/html": [ + "<div>\n", + "<style scoped>\n", + " .dataframe tbody tr th:only-of-type {\n", + " vertical-align: middle;\n", + " }\n", + "\n", + " .dataframe tbody tr th {\n", + " vertical-align: top;\n", + " }\n", + "\n", + " .dataframe thead th {\n", + " text-align: right;\n", + " }\n", + "</style>\n", + "<table border=\"1\" class=\"dataframe\">\n", + " <thead>\n", + " <tr style=\"text-align: right;\">\n", + " <th></th>\n", + " <th>timestamp</th>\n", + " <th>hosts_up</th>\n", + " <th>hosts_down</th>\n", + " <th>servers_pending</th>\n", + " <th>servers_active</th>\n", + " <th>attempts_success</th>\n", + " <th>attempts_failure</th>\n", + " <th>attempts_error</th>\n", + " </tr>\n", + " </thead>\n", + " <tbody>\n", + " <tr>\n", + " <th>0</th>\n", + " <td>1970-01-01 00:05:00+00:00</td>\n", + " <td>1</td>\n", + " <td>0</td>\n", + " <td>44</td>\n", + " <td>0</td>\n", + " <td>0</td>\n", + " <td>0</td>\n", + " <td>0</td>\n", + " </tr>\n", + " <tr>\n", + " <th>1</th>\n", + " <td>1970-01-01 00:10:00+00:00</td>\n", + " <td>1</td>\n", + " <td>0</td>\n", + " <td>29</td>\n", + " <td>15</td>\n", + " <td>15</td>\n", + " <td>0</td>\n", + " <td>0</td>\n", + " </tr>\n", + " <tr>\n", + " <th>2</th>\n", + " <td>1970-01-01 00:15:00+00:00</td>\n", + " <td>1</td>\n", + " <td>0</td>\n", + " <td>29</td>\n", + " <td>15</td>\n", + " <td>15</td>\n", + " <td>0</td>\n", + " <td>0</td>\n", + " </tr>\n", + " <tr>\n", + " <th>3</th>\n", + " <td>1970-01-01 00:20:00+00:00</td>\n", + " <td>1</td>\n", + " <td>0</td>\n", + " <td>29</td>\n", + " <td>15</td>\n", + " <td>15</td>\n", + " <td>0</td>\n", + " <td>0</td>\n", + " </tr>\n", + " <tr>\n", + " <th>4</th>\n", + " <td>1970-01-01 00:25:00+00:00</td>\n", + " <td>1</td>\n", + " <td>0</td>\n", + " <td>29</td>\n", + " <td>15</td>\n", + " <td>15</td>\n", + " <td>0</td>\n", + " <td>0</td>\n", + " </tr>\n", + " <tr>\n", + " <th>...</th>\n", + " <td>...</td>\n", + " <td>...</td>\n", + " <td>...</td>\n", + " <td>...</td>\n", + " <td>...</td>\n", + " <td>...</td>\n", + " <td>...</td>\n", + " <td>...</td>\n", + " </tr>\n", + " <tr>\n", + " <th>8637</th>\n", + " <td>1970-01-30 23:50:00+00:00</td>\n", + " <td>0</td>\n", + " <td>1</td>\n", + " <td>0</td>\n", + " <td>0</td>\n", + " <td>49</td>\n", + " <td>1</td>\n", + " <td>0</td>\n", + " </tr>\n", + " <tr>\n", + " <th>8638</th>\n", + " <td>1970-01-30 23:55:00+00:00</td>\n", + " <td>0</td>\n", + " <td>1</td>\n", + " <td>0</td>\n", + " <td>0</td>\n", + " <td>49</td>\n", + " <td>1</td>\n", + " <td>0</td>\n", + " </tr>\n", + " <tr>\n", + " <th>8639</th>\n", + " <td>1970-01-31 00:00:00+00:00</td>\n", + " <td>0</td>\n", + " <td>1</td>\n", + " <td>0</td>\n", + " <td>0</td>\n", + " <td>49</td>\n", + " <td>1</td>\n", + " <td>0</td>\n", + " </tr>\n", + " <tr>\n", + " <th>8640</th>\n", + " <td>1970-01-31 00:05:00+00:00</td>\n", + " <td>0</td>\n", + " <td>1</td>\n", + " <td>0</td>\n", + " <td>0</td>\n", + " <td>49</td>\n", + " <td>1</td>\n", + " <td>0</td>\n", + " </tr>\n", + " <tr>\n", + " <th>8641</th>\n", + " <td>1970-01-31 00:10:00+00:00</td>\n", + " <td>0</td>\n", + " <td>1</td>\n", + " <td>0</td>\n", + " <td>0</td>\n", + " <td>49</td>\n", + " <td>1</td>\n", + " <td>0</td>\n", + " </tr>\n", + " </tbody>\n", + "</table>\n", + "<p>8642 rows × 8 columns</p>\n", + "</div>" + ], + "text/plain": [ + " timestamp hosts_up hosts_down servers_pending \\\n", + "0 1970-01-01 00:05:00+00:00 1 0 44 \n", + "1 1970-01-01 00:10:00+00:00 1 0 29 \n", + "2 1970-01-01 00:15:00+00:00 1 0 29 \n", + "3 1970-01-01 00:20:00+00:00 1 0 29 \n", + "4 1970-01-01 00:25:00+00:00 1 0 29 \n", + "... ... ... ... ... \n", + "8637 1970-01-30 23:50:00+00:00 0 1 0 \n", + "8638 1970-01-30 23:55:00+00:00 0 1 0 \n", + "8639 1970-01-31 00:00:00+00:00 0 1 0 \n", + "8640 1970-01-31 00:05:00+00:00 0 1 0 \n", + "8641 1970-01-31 00:10:00+00:00 0 1 0 \n", + "\n", + " servers_active attempts_success attempts_failure attempts_error \n", + "0 0 0 0 0 \n", + "1 15 15 0 0 \n", + "2 15 15 0 0 \n", + "3 15 15 0 0 \n", + "4 15 15 0 0 \n", + "... ... ... ... ... \n", + "8637 0 49 1 0 \n", + "8638 0 49 1 0 \n", + "8639 0 49 1 0 \n", + "8640 0 49 1 0 \n", + "8641 0 49 1 0 \n", + "\n", + "[8642 rows x 8 columns]" + ] + }, + "execution_count": 30, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "df_service_single" + ] + }, + { + "cell_type": "code", + "execution_count": 33, + "id": "5b9ccf81", + "metadata": {}, + "outputs": [ + { + "data": { + "text/plain": [ + "0 6189\n", + "1 1828\n", + "2 612\n", + "44 13\n", + "Name: servers_active, dtype: int64" + ] + }, + "execution_count": 33, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "df_service_multi.servers_active.value_counts()" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "9e9bd9b9", + "metadata": {}, + "outputs": [], + "source": [] + } + ], + "metadata": { + "kernelspec": { + "display_name": "Python 3 (ipykernel)", + "language": "python", + "name": "python3" + }, + "language_info": { + "codemirror_mode": { + "name": "ipython", + "version": 3 + }, + "file_extension": ".py", + "mimetype": "text/x-python", + "name": "python", + "nbconvert_exporter": "python", + "pygments_lexer": "ipython3", + "version": "3.10.12" + } + }, + "nbformat": 4, + "nbformat_minor": 5 +} diff --git a/opendc-experiments/opendc-experiments-greenifier/src/main/Python_scripts/OpenDCdemo.ipynb b/opendc-experiments/opendc-experiments-greenifier/src/main/Python_scripts/OpenDCdemo.ipynb new file mode 100644 index 00000000..3a88aca7 --- /dev/null +++ b/opendc-experiments/opendc-experiments-greenifier/src/main/Python_scripts/OpenDCdemo.ipynb @@ -0,0 +1,834 @@ +{ + "cells": [ + { + "cell_type": "code", + "execution_count": 1, + "id": "18170001", + "metadata": {}, + "outputs": [], + "source": [ + "import numpy as np\n", + "import pandas as pd\n", + "import matplotlib.pyplot as plt\n", + "\n", + "from IPython.display import display, HTML\n", + "\n", + "base_folder = \"../../../..\"" + ] + }, + { + "cell_type": "markdown", + "id": "422f4d05", + "metadata": {}, + "source": [ + "## Topologies" + ] + }, + { + "cell_type": "code", + "execution_count": 2, + "id": "a2d05361", + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "Topology name: multi\n" + ] + }, + { + "data": { + "text/html": [ + "<table border=\"1\" class=\"dataframe\">\n", + " <thead>\n", + " <tr style=\"text-align: right;\">\n", + " <th></th>\n", + " <th>ClusterID</th>\n", + " <th>ClusterName</th>\n", + " <th>Cores</th>\n", + " <th>Speed</th>\n", + " <th>Memory</th>\n", + " <th>numberOfHosts</th>\n", + " <th>memoryCapacityPerHost</th>\n", + " <th>coreCountPerHost</th>\n", + " </tr>\n", + " </thead>\n", + " <tbody>\n", + " <tr>\n", + " <th>0</th>\n", + " <td>A01</td>\n", + " <td>A01</td>\n", + " <td>32</td>\n", + " <td>3.20</td>\n", + " <td>2048</td>\n", + " <td>1</td>\n", + " <td>256</td>\n", + " <td>32</td>\n", + " </tr>\n", + " <tr>\n", + " <th>1</th>\n", + " <td>B01</td>\n", + " <td>B01</td>\n", + " <td>48</td>\n", + " <td>2.93</td>\n", + " <td>1256</td>\n", + " <td>6</td>\n", + " <td>64</td>\n", + " <td>8</td>\n", + " </tr>\n", + " <tr>\n", + " <th>2</th>\n", + " <td>C01</td>\n", + " <td>C01</td>\n", + " <td>32</td>\n", + " <td>3.20</td>\n", + " <td>2048</td>\n", + " <td>2</td>\n", + " <td>128</td>\n", + " <td>16</td>\n", + " </tr>\n", + " </tbody>\n", + "</table>" + ], + "text/plain": [ + "<IPython.core.display.HTML object>" + ] + }, + "metadata": {}, + "output_type": "display_data" + }, + { + "name": "stdout", + "output_type": "stream", + "text": [ + "Topology name: single\n" + ] + }, + { + "data": { + "text/html": [ + "<table border=\"1\" class=\"dataframe\">\n", + " <thead>\n", + " <tr style=\"text-align: right;\">\n", + " <th></th>\n", + " <th>ClusterID</th>\n", + " <th>ClusterName</th>\n", + " <th>Cores</th>\n", + " <th>Speed</th>\n", + " <th>Memory</th>\n", + " <th>numberOfHosts</th>\n", + " <th>memoryCapacityPerHost</th>\n", + " <th>coreCountPerHost</th>\n", + " </tr>\n", + " </thead>\n", + " <tbody>\n", + " <tr>\n", + " <th>0</th>\n", + " <td>A01</td>\n", + " <td>A01</td>\n", + " <td>8</td>\n", + " <td>3.2</td>\n", + " <td>128</td>\n", + " <td>1</td>\n", + " <td>128</td>\n", + " <td>8</td>\n", + " </tr>\n", + " </tbody>\n", + "</table>" + ], + "text/plain": [ + "<IPython.core.display.HTML object>" + ] + }, + "metadata": {}, + "output_type": "display_data" + } + ], + "source": [ + "def read_topology(topology_name):\n", + " print(f\"Topology name: {topology_name}\")\n", + " df = pd.read_csv(f\"{base_folder}/resources/env/{topology_name}.txt\", delimiter=\";\")\n", + " display(HTML(df.to_html()))\n", + " \n", + "read_topology(\"multi\")\n", + "read_topology(\"single\")" + ] + }, + { + "cell_type": "markdown", + "id": "8f4fe54d", + "metadata": {}, + "source": [ + "## Traces" + ] + }, + { + "cell_type": "code", + "execution_count": 3, + "id": "fd17d88a", + "metadata": {}, + "outputs": [ + { + "data": { + "text/html": [ + "<div>\n", + "<style scoped>\n", + " .dataframe tbody tr th:only-of-type {\n", + " vertical-align: middle;\n", + " }\n", + "\n", + " .dataframe tbody tr th {\n", + " vertical-align: top;\n", + " }\n", + "\n", + " .dataframe thead th {\n", + " text-align: right;\n", + " }\n", + "</style>\n", + "<table border=\"1\" class=\"dataframe\">\n", + " <thead>\n", + " <tr style=\"text-align: right;\">\n", + " <th></th>\n", + " <th>id</th>\n", + " <th>timestamp</th>\n", + " <th>duration</th>\n", + " <th>cpu_count</th>\n", + " <th>cpu_usage</th>\n", + " </tr>\n", + " </thead>\n", + " <tbody>\n", + " <tr>\n", + " <th>0</th>\n", + " <td>1019</td>\n", + " <td>2013-08-12 13:40:46+00:00</td>\n", + " <td>300000</td>\n", + " <td>1</td>\n", + " <td>0.000000</td>\n", + " </tr>\n", + " <tr>\n", + " <th>1</th>\n", + " <td>1019</td>\n", + " <td>2013-08-12 13:45:46+00:00</td>\n", + " <td>300000</td>\n", + " <td>1</td>\n", + " <td>11.703998</td>\n", + " </tr>\n", + " <tr>\n", + " <th>2</th>\n", + " <td>1019</td>\n", + " <td>2013-08-12 13:55:46+00:00</td>\n", + " <td>600000</td>\n", + " <td>1</td>\n", + " <td>0.000000</td>\n", + " </tr>\n", + " <tr>\n", + " <th>3</th>\n", + " <td>1019</td>\n", + " <td>2013-08-12 14:00:46+00:00</td>\n", + " <td>300000</td>\n", + " <td>1</td>\n", + " <td>11.703998</td>\n", + " </tr>\n", + " <tr>\n", + " <th>4</th>\n", + " <td>1019</td>\n", + " <td>2013-08-12 14:15:46+00:00</td>\n", + " <td>900000</td>\n", + " <td>1</td>\n", + " <td>0.000000</td>\n", + " </tr>\n", + " </tbody>\n", + "</table>\n", + "</div>" + ], + "text/plain": [ + " id timestamp duration cpu_count cpu_usage\n", + "0 1019 2013-08-12 13:40:46+00:00 300000 1 0.000000\n", + "1 1019 2013-08-12 13:45:46+00:00 300000 1 11.703998\n", + "2 1019 2013-08-12 13:55:46+00:00 600000 1 0.000000\n", + "3 1019 2013-08-12 14:00:46+00:00 300000 1 11.703998\n", + "4 1019 2013-08-12 14:15:46+00:00 900000 1 0.000000" + ] + }, + "execution_count": 3, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "df_trace = pd.read_parquet(f\"{base_folder}/resources/bitbrains-small/trace/trace.parquet\")\n", + "df_trace.head()" + ] + }, + { + "cell_type": "code", + "execution_count": 4, + "id": "346f097f", + "metadata": { + "scrolled": true + }, + "outputs": [ + { + "data": { + "text/html": [ + "<div>\n", + "<style scoped>\n", + " .dataframe tbody tr th:only-of-type {\n", + " vertical-align: middle;\n", + " }\n", + "\n", + " .dataframe tbody tr th {\n", + " vertical-align: top;\n", + " }\n", + "\n", + " .dataframe thead th {\n", + " text-align: right;\n", + " }\n", + "</style>\n", + "<table border=\"1\" class=\"dataframe\">\n", + " <thead>\n", + " <tr style=\"text-align: right;\">\n", + " <th></th>\n", + " <th>id</th>\n", + " <th>start_time</th>\n", + " <th>stop_time</th>\n", + " <th>cpu_count</th>\n", + " <th>cpu_capacity</th>\n", + " <th>mem_capacity</th>\n", + " </tr>\n", + " </thead>\n", + " <tbody>\n", + " <tr>\n", + " <th>0</th>\n", + " <td>1019</td>\n", + " <td>2013-08-12 13:35:46+00:00</td>\n", + " <td>2013-09-11 13:39:58+00:00</td>\n", + " <td>1</td>\n", + " <td>2926.000135</td>\n", + " <td>181352</td>\n", + " </tr>\n", + " <tr>\n", + " <th>1</th>\n", + " <td>1023</td>\n", + " <td>2013-08-12 13:35:46+00:00</td>\n", + " <td>2013-09-11 13:39:58+00:00</td>\n", + " <td>1</td>\n", + " <td>2925.999560</td>\n", + " <td>260096</td>\n", + " </tr>\n", + " <tr>\n", + " <th>2</th>\n", + " <td>1026</td>\n", + " <td>2013-08-12 13:35:46+00:00</td>\n", + " <td>2013-09-11 13:39:58+00:00</td>\n", + " <td>1</td>\n", + " <td>2925.999717</td>\n", + " <td>249972</td>\n", + " </tr>\n", + " <tr>\n", + " <th>3</th>\n", + " <td>1052</td>\n", + " <td>2013-08-29 14:38:12+00:00</td>\n", + " <td>2013-09-05 07:09:07+00:00</td>\n", + " <td>1</td>\n", + " <td>2926.000107</td>\n", + " <td>131245</td>\n", + " </tr>\n", + " <tr>\n", + " <th>4</th>\n", + " <td>1073</td>\n", + " <td>2013-08-21 11:07:12+00:00</td>\n", + " <td>2013-09-11 13:39:58+00:00</td>\n", + " <td>1</td>\n", + " <td>2599.999649</td>\n", + " <td>179306</td>\n", + " </tr>\n", + " </tbody>\n", + "</table>\n", + "</div>" + ], + "text/plain": [ + " id start_time stop_time cpu_count \\\n", + "0 1019 2013-08-12 13:35:46+00:00 2013-09-11 13:39:58+00:00 1 \n", + "1 1023 2013-08-12 13:35:46+00:00 2013-09-11 13:39:58+00:00 1 \n", + "2 1026 2013-08-12 13:35:46+00:00 2013-09-11 13:39:58+00:00 1 \n", + "3 1052 2013-08-29 14:38:12+00:00 2013-09-05 07:09:07+00:00 1 \n", + "4 1073 2013-08-21 11:07:12+00:00 2013-09-11 13:39:58+00:00 1 \n", + "\n", + " cpu_capacity mem_capacity \n", + "0 2926.000135 181352 \n", + "1 2925.999560 260096 \n", + "2 2925.999717 249972 \n", + "3 2926.000107 131245 \n", + "4 2599.999649 179306 " + ] + }, + "execution_count": 4, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "df_meta = pd.read_parquet(f\"{base_folder}/resources/bitbrains-small/trace/meta.parquet\")\n", + "df_meta.head()" + ] + }, + { + "cell_type": "markdown", + "id": "13bf9fdb", + "metadata": {}, + "source": [ + "# Lets run this in OpenDC!" + ] + }, + { + "cell_type": "markdown", + "id": "c9766446", + "metadata": {}, + "source": [ + "## Resulting Files" + ] + }, + { + "cell_type": "code", + "execution_count": 43, + "id": "0d400ffd", + "metadata": {}, + "outputs": [], + "source": [ + "output_folder = f\"{base_folder}/output\"\n", + "workload = \"workload=bitbrains-small\"\n", + "seed = \"seed=0\"\n", + "\n", + "df_host_single = pd.read_parquet(f\"{output_folder}/host/topology=single/{workload}/{seed}/data.parquet\")\n", + "df_host_multi = pd.read_parquet(f\"{output_folder}/host/topology=multi/{workload}/{seed}/data.parquet\")\n", + "\n", + "df_server_single = pd.read_parquet(f\"{output_folder}/server/topology=single/{workload}/{seed}/data.parquet\")\n", + "df_server_multi = pd.read_parquet(f\"{output_folder}/server/topology=multi/{workload}/{seed}/data.parquet\")\n", + "\n", + "df_service_single = pd.read_parquet(f\"{output_folder}/service/topology=single/{workload}/{seed}/data.parquet\")\n", + "df_service_multi = pd.read_parquet(f\"{output_folder}/service/topology=multi/{workload}/{seed}/data.parquet\")\n", + "\n", + "def add_absolute_timestamp(df, start_dt):\n", + " df[\"absolute_timestamp\"] = start_dt + (df[\"timestamp\"] - df[\"timestamp\"].min())\n", + "\n", + "add_absolute_timestamp(df_host_single, df_meta[\"start_time\"].min())\n", + "add_absolute_timestamp(df_host_single, df_meta[\"start_time\"].min())\n", + "\n", + "add_absolute_timestamp(df_server_single, df_meta[\"start_time\"].min())\n", + "add_absolute_timestamp(df_server_multi, df_meta[\"start_time\"].min())\n", + "\n", + "add_absolute_timestamp(df_service_single, df_meta[\"start_time\"].min())\n", + "add_absolute_timestamp(df_service_multi, df_meta[\"start_time\"].min())" + ] + }, + { + "cell_type": "markdown", + "id": "6d494d6e", + "metadata": {}, + "source": [ + "### Host" + ] + }, + { + "cell_type": "code", + "execution_count": 44, + "id": "48a1e1a6", + "metadata": {}, + "outputs": [ + { + "data": { + "text/plain": [ + "['timestamp',\n", + " 'host_id',\n", + " 'cpu_count',\n", + " 'mem_capacity',\n", + " 'guests_terminated',\n", + " 'guests_running',\n", + " 'guests_error',\n", + " 'guests_invalid',\n", + " 'cpu_limit',\n", + " 'cpu_usage',\n", + " 'cpu_demand',\n", + " 'cpu_utilization',\n", + " 'cpu_time_active',\n", + " 'cpu_time_idle',\n", + " 'cpu_time_steal',\n", + " 'cpu_time_lost',\n", + " 'power_total',\n", + " 'uptime',\n", + " 'downtime',\n", + " 'boot_time']" + ] + }, + "metadata": {}, + "output_type": "display_data" + }, + { + "name": "stdout", + "output_type": "stream", + "text": [ + "number of measurements: 77859\n" + ] + } + ], + "source": [ + "display(list(df_host_multi.columns))\n", + "print(f\"number of measurements: {len(df_host_multi)}\")" + ] + }, + { + "cell_type": "markdown", + "id": "9eb9be2c", + "metadata": {}, + "source": [ + "### Server" + ] + }, + { + "cell_type": "code", + "execution_count": 45, + "id": "57a2b148", + "metadata": {}, + "outputs": [ + { + "data": { + "text/plain": [ + "['timestamp',\n", + " 'server_id',\n", + " 'host_id',\n", + " 'mem_capacity',\n", + " 'cpu_count',\n", + " 'cpu_limit',\n", + " 'cpu_time_active',\n", + " 'cpu_time_idle',\n", + " 'cpu_time_steal',\n", + " 'cpu_time_lost',\n", + " 'uptime',\n", + " 'downtime',\n", + " 'provision_time',\n", + " 'boot_time',\n", + " 'absolute_timestamp']" + ] + }, + "metadata": {}, + "output_type": "display_data" + }, + { + "name": "stdout", + "output_type": "stream", + "text": [ + "number of measurements: 408490\n" + ] + } + ], + "source": [ + "display(list(df_server_multi.columns))\n", + "print(f\"number of measurements: {len(df_server_multi)}\")" + ] + }, + { + "cell_type": "markdown", + "id": "fbe5f439", + "metadata": {}, + "source": [ + "### Service" + ] + }, + { + "cell_type": "code", + "execution_count": 46, + "id": "9ef468ed", + "metadata": { + "scrolled": false + }, + "outputs": [ + { + "data": { + "text/plain": [ + "['timestamp',\n", + " 'hosts_up',\n", + " 'hosts_down',\n", + " 'servers_pending',\n", + " 'servers_active',\n", + " 'attempts_success',\n", + " 'attempts_failure',\n", + " 'attempts_error',\n", + " 'absolute_timestamp']" + ] + }, + "metadata": {}, + "output_type": "display_data" + }, + { + "name": "stdout", + "output_type": "stream", + "text": [ + "number of measurements: 8651\n" + ] + } + ], + "source": [ + "display(list(df_service_single.columns))\n", + "print(f\"number of measurements: {len(df_host_single)}\")" + ] + }, + { + "cell_type": "markdown", + "id": "09d31c91", + "metadata": {}, + "source": [ + "## Power Usage" + ] + }, + { + "cell_type": "code", + "execution_count": 47, + "id": "82f0a24a", + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "single topology: 822692246.2425151\n", + "multi topology: 5870271518.168591\n" + ] + } + ], + "source": [ + "print(f\"single topology: {df_host_single.power_total.sum()}\")\n", + "print(f\"multi topology: {df_host_multi.power_total.sum()}\")" + ] + }, + { + "cell_type": "markdown", + "id": "7ab3357d", + "metadata": {}, + "source": [ + "## CPU usage" + ] + }, + { + "cell_type": "code", + "execution_count": 48, + "id": "e94db3a6", + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "single topology: 0.7799672554077309\n", + "multi topology: 0.3421434368579651\n" + ] + } + ], + "source": [ + "print(f\"single topology: {df_host_single.cpu_utilization.mean()}\")\n", + "print(f\"multi topology: {df_host_multi.cpu_utilization.mean()}\")" + ] + }, + { + "cell_type": "markdown", + "id": "e000a260", + "metadata": {}, + "source": [ + "## CPU utilization" + ] + }, + { + "cell_type": "code", + "execution_count": 49, + "id": "8d7daa45", + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "multi topology: 0.3421434368579651\n", + "single topology: 0.7799672554077309\n" + ] + } + ], + "source": [ + "print(f\"multi topology: {df_host_multi.cpu_utilization.mean()}\")\n", + "print(f\"single topology: {df_host_single.cpu_utilization.mean()}\")" + ] + }, + { + "cell_type": "markdown", + "id": "ad97741c", + "metadata": {}, + "source": [ + "## Plotting Results" + ] + }, + { + "cell_type": "code", + "execution_count": 33, + "id": "5df8f9aa", + "metadata": {}, + "outputs": [ + { + "data": { + "image/png": "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CuEGAACYCuEGAACYCuEGAACYCuEGAACYCuEGAACYSpcIN/n5+YqJiZG/v78SExO1bdu2c/ZdtWqVLBZLs83f39+D1QIAgK7M6+FmzZo1stvtys7O1o4dOzRq1CilpKTo2LFj59wnKChIFRUVzu3w4cMerBgAAHRlXg83ubm5yszMVEZGhkaMGKGCggIFBASosLDwnPtYLBZFREQ4t/DwcA9WDAAAujKvhpv6+npt375dycnJzjYfHx8lJyertLT0nPudOnVKl1xyiaKjozVp0iR98MEH5+xbV1enmpqaZhsAADAvr4ab48ePq7GxscXMS3h4uBwOR6v7DBs2TIWFhfrLX/6iP/7xj2pqatKYMWP0ySeftNo/JydHwcHBzi06Otrt1wEAALoOr9+Waq+kpCSlpaUpNjZW11xzjdatW6d+/frpt7/9bav9Z82aperqaud25MgRD1cMAAA8qYc3Tx4aGiqr1arKyspm7ZWVlYqIiGjTMXx9ffWtb31L+/fvb/V9Pz8/+fn5dbhWAADQPXh15sZmsykuLk7FxcXOtqamJhUXFyspKalNx2hsbNTu3bsVGRnZWWUCAIBuxKszN5Jkt9uVnp6u+Ph4JSQkKC8vT7W1tcrIyJAkpaWlKSoqSjk5OZKkefPm6dvf/rYGDx6sEydOaPHixTp8+LDuuusub14GAADoIrweblJTU1VVVaW5c+fK4XAoNjZWRUVFzkXG5eXl8vH5coLps88+U2ZmphwOh3r37q24uDht3bpVI0aM8NYlAACALsRiGIbh7SI8qaamRsHBwaqurlZQUJC3ywEAoGt6IdX1faescV8d/689n9/d7ttSAAAA50O4AQAApkK4AQAApkK4AQAApkK4AQAApkK4AQAApkK4AQAApkK4AQAApkK4AQAApkK4AQAApkK4AQAApkK4AQAApkK4AQAApkK4AQAApkK4AQAApkK4AQAApkK4AQAApkK4AQAAptLD2wWYzbRV77i874qpo91YCQAAFyZmbgAAgKkQbgAAgKkQbgAAgKkQbgAAgKm4FG4OHjzo7joAAADcwqVwM3jwYF133XX64x//qM8//9zdNQEAALjMpXCzY8cOXXnllbLb7YqIiNA999yjbdu2ubs2AACAdnMp3MTGxurJJ5/U0aNHVVhYqIqKCl199dUaOXKkcnNzVVVV5e46AQAA2qRD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", + "text/plain": [ + "<Figure size 640x480 with 1 Axes>" + ] + }, + "metadata": {}, + "output_type": "display_data" + } + ], + "source": [ + "data = df_host_multi.cpu_utilization\n", + "plt.hist(data, weights=np.ones_like(data) / len(data),\n", + " alpha=0.7, label=\"big\", bins=30)\n", + "\n", + "\n", + "data = df_host_single.cpu_utilization\n", + "plt.hist(data, weights=np.ones_like(data) / len(data),\n", + " alpha=0.7, label=\"small\", bins=30)\n", + "\n", + "plt.xlabel(\"CPU utilization\")\n", + "plt.ylabel(\"Frequency\")\n", + "plt.legend()\n", + "plt.show()" + ] + }, + { + "cell_type": "code", + "execution_count": 34, + "id": "520e42a4", + "metadata": {}, + "outputs": [ + { + "data": { + "text/plain": [ + "1.000000 6146\n", + "0.000009 9\n", + "0.002294 2\n", + "0.027410 2\n", + "0.021973 2\n", + " ... \n", + "0.028164 1\n", + "0.029120 1\n", + "0.028367 1\n", + "0.030243 1\n", + "0.030289 1\n", + "Name: cpu_utilization, Length: 2488, dtype: int64" + ] + }, + "execution_count": 34, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "df_host_single.cpu_utilization.value_counts()" + ] + }, + { + "cell_type": "code", + "execution_count": 35, + "id": "a8c35267", + "metadata": {}, + "outputs": [ + { + "data": { + "text/plain": [ + "array([b'\\xf8\\x8b\\xb8\\xa8rL\\x81\\xec\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x02',\n", + " b'\\x1b9\\x89jQ\\xa8t\\x9b\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x03',\n", + " b'\\xc5\\x84\\x13:\\xc9\\x16\\xab<\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00',\n", + " b'S\\xcb\\x9f\\x0ct~\\xa2\\xea\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x04',\n", + " b'\\xe2 \\xa89{\\x1d\\xcd\\xaf\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00',\n", + " b'\\x06\\xc4]\\x18\\x80\\tEO\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x01',\n", + " b',\\x82\\x9a\\xbe\\x1fE2\\xe1\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x05',\n", + " b'>\\xe5x\\x90A\\xc9\\x8a\\xc3\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x01',\n", + " b'nx\\x9ej\\xa1\\xb9e\\xf4\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00'],\n", + " dtype=object)" + ] + }, + "execution_count": 35, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "df_host_multi.host_id.unique()" + ] + }, + { + "cell_type": "code", + "execution_count": 54, + "id": "68546b09", + "metadata": {}, + "outputs": [ + { + "data": { + "text/plain": [ + "1 141065\n", + "4 118263\n", + "8 77859\n", + "2 62652\n", + "32 8651\n", + "Name: cpu_count, dtype: int64" + ] + }, + "execution_count": 54, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "df_server_single.cpu_count.value_counts()" + ] + }, + { + "cell_type": "code", + "execution_count": 56, + "id": "326abd0c", + "metadata": {}, + "outputs": [ + { + "data": { + "text/plain": [ + "8 8651\n", + "Name: cpu_count, dtype: int64" + ] + }, + "execution_count": 56, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "df_host_single.cpu_count.value_counts()" + ] + } + ], + "metadata": { + "kernelspec": { + "display_name": "Python 3 (ipykernel)", + "language": "python", + "name": "python3" + }, + "language_info": { + "codemirror_mode": { + "name": "ipython", + "version": 3 + }, + "file_extension": ".py", + "mimetype": "text/x-python", + "name": "python", + "nbconvert_exporter": "python", + "pygments_lexer": "ipython3", + "version": "3.10.12" + } + }, + "nbformat": 4, + "nbformat_minor": 5 +} diff --git a/opendc-experiments/opendc-experiments-greenifier/src/main/kotlin/org/opendc/experiments/greenifier/GreenifierCli.kt b/opendc-experiments/opendc-experiments-greenifier/src/main/kotlin/org/opendc/experiments/greenifier/GreenifierCli.kt new file mode 100644 index 00000000..1f5a40f1 --- /dev/null +++ b/opendc-experiments/opendc-experiments-greenifier/src/main/kotlin/org/opendc/experiments/greenifier/GreenifierCli.kt @@ -0,0 +1,154 @@ +/* + * Copyright (c) 2022 AtLarge Research + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ + +@file:JvmName("GreenifierCli") + +package org.opendc.experiments.greenifier + +import com.github.ajalt.clikt.core.CliktCommand +import com.github.ajalt.clikt.parameters.arguments.argument +import com.github.ajalt.clikt.parameters.arguments.default +import com.github.ajalt.clikt.parameters.options.associate +import com.github.ajalt.clikt.parameters.options.default +import com.github.ajalt.clikt.parameters.options.defaultLazy +import com.github.ajalt.clikt.parameters.options.flag +import com.github.ajalt.clikt.parameters.options.option +import com.github.ajalt.clikt.parameters.types.choice +import com.github.ajalt.clikt.parameters.types.file +import com.github.ajalt.clikt.parameters.types.int +import com.github.ajalt.clikt.parameters.types.long +import me.tongfei.progressbar.ProgressBarBuilder +import me.tongfei.progressbar.ProgressBarStyle +import org.opendc.experiments.greenifier.model.Scenario +import org.opendc.experiments.greenifier.portfolio.GreenifierPortfolio +import java.io.File +import java.util.concurrent.ForkJoinPool +import java.util.stream.LongStream + +/** + * Main entrypoint of the application. + */ +fun main(args: Array<String>): Unit = GreenifierCommand().main(args) + +/** + * Represents the command for the Greenifier experiments. + */ +internal class GreenifierCommand : CliktCommand(name = "greenifier") { + /** + * The path to the environment directory. + */ + private val envPath by option("--env-path", help = "path to environment directory") + .file(canBeDir = true, canBeFile = false) + .defaultLazy { File("input/environments") } + + /** + * The path to the trace directory. + */ + private val tracePath by option("--trace-path", help = "path to trace directory") + .file(canBeDir = true, canBeFile = false) + .defaultLazy { File("input/traces") } + + /** + * The path to the experiment output. + */ + private val outputPath by option("-O", "--output", help = "path to experiment output") + .file(canBeDir = true, canBeFile = false) + .defaultLazy { File("output") } + + /** + * Disable writing output. + */ + private val disableOutput by option("--disable-output", help = "disable output").flag() + + /** + * The number of threads to use for parallelism. + */ + private val parallelism by option("-p", "--parallelism", help = "number of worker threads") + .int() + .default(Runtime.getRuntime().availableProcessors() - 1) + + /** + * The number of repeats. + */ + private val repeats by option("-r", "--repeats", help = "number of repeats") + .int() + .default(128) + + /** + * The seed for seeding the random instances. + */ + private val seed by option("-s", "--seed", help = "initial seed for randomness") + .long() + .default(0) + + /** + * The portfolio to replay. + */ + private val portfolio by argument(help = "portfolio to replay") + .choice( + "greenifier" to { GreenifierPortfolio() }, + ) + .default({GreenifierPortfolio()}) + + /** + * The base partitions to use for the invocation + */ + private val basePartitions: Map<String, String> by option("-P", "--base-partitions").associate() + + override fun run() { + val runner = GreenifierRunner(envPath, tracePath, outputPath.takeUnless { disableOutput }) + val scenarios = portfolio().scenarios.toList() + + val pool = ForkJoinPool(parallelism) + + echo("Detected ${scenarios.size} scenarios [$repeats replications]") + + for (scenario in scenarios) { + runScenario(runner, pool, scenario) + } + + pool.shutdown() + } + + /** + * Run a single scenario. + */ + private fun runScenario(runner: GreenifierRunner, pool: ForkJoinPool, scenario: Scenario) { + val pb = ProgressBarBuilder() + .setInitialMax(repeats.toLong()) + .setStyle(ProgressBarStyle.ASCII) + .setTaskName("Simulating...") + .build() + + pool.submit { + LongStream.range(0, repeats.toLong()) + .parallel() + .forEach { repeat -> + val augmentedScenario = scenario.copy(partitions = basePartitions + scenario.partitions) + runner.runScenario(augmentedScenario, seed + repeat) + pb.step() + } + + pb.close() + }.join() + } +} diff --git a/opendc-experiments/opendc-experiments-greenifier/src/main/kotlin/org/opendc/experiments/greenifier/GreenifierRunner.kt b/opendc-experiments/opendc-experiments-greenifier/src/main/kotlin/org/opendc/experiments/greenifier/GreenifierRunner.kt new file mode 100644 index 00000000..4811cb61 --- /dev/null +++ b/opendc-experiments/opendc-experiments-greenifier/src/main/kotlin/org/opendc/experiments/greenifier/GreenifierRunner.kt @@ -0,0 +1,102 @@ +/* + * Copyright (c) 2022 AtLarge Research + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ + +package org.opendc.experiments.greenifier + +import org.opendc.compute.service.ComputeService +import org.opendc.experiments.greenifier.model.Scenario +import org.opendc.experiments.greenifier.topology.clusterTopology +import org.opendc.experiments.compute.ComputeWorkloadLoader +import org.opendc.experiments.compute.createComputeScheduler +import org.opendc.experiments.compute.export.parquet.ParquetComputeMonitor +import org.opendc.experiments.compute.grid5000 +import org.opendc.experiments.compute.registerComputeMonitor +import org.opendc.experiments.compute.replay +import org.opendc.experiments.compute.setupComputeService +import org.opendc.experiments.compute.setupHosts +import org.opendc.experiments.provisioner.Provisioner +import org.opendc.simulator.kotlin.runSimulation +import java.io.File +import java.time.Duration +import java.util.Random +import kotlin.math.roundToLong + +/** + * Helper class for running the Greenifier experiments. + * + * @param envPath The path to the directory containing the environments. + * @param tracePath The path to the directory containing the traces. + * @param outputPath The path to the directory where the output should be written (or `null` if no output should be generated). + */ +public class GreenifierRunner( + private val envPath: File, + tracePath: File, + private val outputPath: File? +) { + /** + * The [ComputeWorkloadLoader] to use for loading the traces. + */ + private val workloadLoader = ComputeWorkloadLoader(tracePath) + + /** + * Run a single [scenario] with the specified seed. + */ + fun runScenario(scenario: Scenario, seed: Long) = runSimulation { + val serviceDomain = "compute.opendc.org" + val topology = clusterTopology(File(envPath, "${scenario.topology.name}.txt")) + + Provisioner(dispatcher, seed).use { provisioner -> + provisioner.runSteps( + setupComputeService(serviceDomain, { createComputeScheduler(scenario.allocationPolicy, Random(it.seeder.nextLong())) }), + setupHosts(serviceDomain, topology, optimize = true) + ) + + if (outputPath != null) { + val partitions = scenario.partitions + ("seed" to seed.toString()) + val partition = partitions.map { (k, v) -> "$k=$v" }.joinToString("/") + + provisioner.runStep( + registerComputeMonitor( + serviceDomain, + ParquetComputeMonitor( + outputPath, + partition, + bufferSize = 4096 + ) + ) + ) + } + + val service = provisioner.registry.resolve(serviceDomain, ComputeService::class.java)!! + val vms = scenario.workload.source.resolve(workloadLoader, Random(seed)) + val operationalPhenomena = scenario.operationalPhenomena + val failureModel = + if (operationalPhenomena.failureFrequency > 0) { + grid5000(Duration.ofSeconds((operationalPhenomena.failureFrequency * 60).roundToLong())) + } else { + null + } + + service.replay(timeSource, vms, seed, failureModel = failureModel, interference = operationalPhenomena.hasInterference) + } + } +} diff --git a/opendc-experiments/opendc-experiments-greenifier/src/main/kotlin/org/opendc/experiments/greenifier/model/OperationalPhenomena.kt b/opendc-experiments/opendc-experiments-greenifier/src/main/kotlin/org/opendc/experiments/greenifier/model/OperationalPhenomena.kt new file mode 100644 index 00000000..c5a8acc7 --- /dev/null +++ b/opendc-experiments/opendc-experiments-greenifier/src/main/kotlin/org/opendc/experiments/greenifier/model/OperationalPhenomena.kt @@ -0,0 +1,31 @@ +/* + * Copyright (c) 2021 AtLarge Research + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ + +package org.opendc.experiments.greenifier.model + +/** + * Operation phenomena during experiments. + * + * @param failureFrequency The average time between failures in hours. + * @param hasInterference A flag to enable performance interference between VMs. + */ +public data class OperationalPhenomena(val failureFrequency: Double, val hasInterference: Boolean) diff --git a/opendc-experiments/opendc-experiments-greenifier/src/main/kotlin/org/opendc/experiments/greenifier/model/Scenario.kt b/opendc-experiments/opendc-experiments-greenifier/src/main/kotlin/org/opendc/experiments/greenifier/model/Scenario.kt new file mode 100644 index 00000000..4f31aeb8 --- /dev/null +++ b/opendc-experiments/opendc-experiments-greenifier/src/main/kotlin/org/opendc/experiments/greenifier/model/Scenario.kt @@ -0,0 +1,40 @@ +/* + * Copyright (c) 2022 AtLarge Research + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ + +package org.opendc.experiments.greenifier.model + +/** + * A single scenario of a portfolio. + * + * @property topology The topology to test. + * @property workload The workload to test. + * @property operationalPhenomena The [OperationalPhenomena] to model. + * @property allocationPolicy The allocation policy of the scheduler. + * @property partitions The partition of the scenario. + */ +public data class Scenario( + val topology: Topology, + val workload: Workload, + val operationalPhenomena: OperationalPhenomena, + val allocationPolicy: String, + val partitions: Map<String, String> = emptyMap() +) diff --git a/opendc-experiments/opendc-experiments-greenifier/src/main/kotlin/org/opendc/experiments/greenifier/model/Topology.kt b/opendc-experiments/opendc-experiments-greenifier/src/main/kotlin/org/opendc/experiments/greenifier/model/Topology.kt new file mode 100644 index 00000000..aa2e5190 --- /dev/null +++ b/opendc-experiments/opendc-experiments-greenifier/src/main/kotlin/org/opendc/experiments/greenifier/model/Topology.kt @@ -0,0 +1,28 @@ +/* + * Copyright (c) 2021 AtLarge Research + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ + +package org.opendc.experiments.greenifier.model + +/** + * The topology on which we simulate the workload. + */ +public data class Topology(val name: String) diff --git a/opendc-experiments/opendc-experiments-greenifier/src/main/kotlin/org/opendc/experiments/greenifier/model/Workload.kt b/opendc-experiments/opendc-experiments-greenifier/src/main/kotlin/org/opendc/experiments/greenifier/model/Workload.kt new file mode 100644 index 00000000..c222a28d --- /dev/null +++ b/opendc-experiments/opendc-experiments-greenifier/src/main/kotlin/org/opendc/experiments/greenifier/model/Workload.kt @@ -0,0 +1,33 @@ +/* + * Copyright (c) 2021 AtLarge Research + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ + +package org.opendc.experiments.greenifier.model + +import org.opendc.experiments.compute.ComputeWorkload + +/** + * A single workload originating from a trace. + * + * @param name the name of the workload. + * @param source The source of the workload data. + */ +data class Workload(val name: String, val source: ComputeWorkload) diff --git a/opendc-experiments/opendc-experiments-greenifier/src/main/kotlin/org/opendc/experiments/greenifier/portfolio/GreenifierPortfolio.kt b/opendc-experiments/opendc-experiments-greenifier/src/main/kotlin/org/opendc/experiments/greenifier/portfolio/GreenifierPortfolio.kt new file mode 100644 index 00000000..44fd359a --- /dev/null +++ b/opendc-experiments/opendc-experiments-greenifier/src/main/kotlin/org/opendc/experiments/greenifier/portfolio/GreenifierPortfolio.kt @@ -0,0 +1,58 @@ +/* + * Copyright (c) 2022 AtLarge Research + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ + +package org.opendc.experiments.greenifier.portfolio + +import org.opendc.experiments.greenifier.model.OperationalPhenomena +import org.opendc.experiments.greenifier.model.Scenario +import org.opendc.experiments.greenifier.model.Topology +import org.opendc.experiments.greenifier.model.Workload +import org.opendc.experiments.compute.sampleByLoad +import org.opendc.experiments.compute.trace + +/** + * A [Portfolio] that explores the difference between horizontal and vertical scaling. + */ +public class GreenifierPortfolio : Portfolio { + private val topologies = listOf( + Topology("single"), + Topology("multi"), + ) + + private val workloads = listOf( + Workload("bitbrains-small", trace("trace").sampleByLoad(1.0)), + ) + private val operationalPhenomena = OperationalPhenomena(0.0, false) + private val allocationPolicy = "active-servers" + + override val scenarios: Iterable<Scenario> = topologies.flatMap { topology -> + workloads.map { workload -> + Scenario( + topology, + workload, + operationalPhenomena, + allocationPolicy, + mapOf("topology" to topology.name, "workload" to workload.name) + ) + } + } +} diff --git a/opendc-experiments/opendc-experiments-greenifier/src/main/kotlin/org/opendc/experiments/greenifier/portfolio/Portfolio.kt b/opendc-experiments/opendc-experiments-greenifier/src/main/kotlin/org/opendc/experiments/greenifier/portfolio/Portfolio.kt new file mode 100644 index 00000000..e2875c9c --- /dev/null +++ b/opendc-experiments/opendc-experiments-greenifier/src/main/kotlin/org/opendc/experiments/greenifier/portfolio/Portfolio.kt @@ -0,0 +1,35 @@ +/* + * Copyright (c) 2021 AtLarge Research + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ + +package org.opendc.experiments.greenifier.portfolio + +import org.opendc.experiments.greenifier.model.Scenario + +/** + * A portfolio represents a collection of scenarios are tested for the work. + */ +public interface Portfolio { + /** + * The scenarios that belong to this portfolio. + */ + val scenarios: Iterable<Scenario> +} diff --git a/opendc-experiments/opendc-experiments-greenifier/src/main/kotlin/org/opendc/experiments/greenifier/topology/ClusterSpec.kt b/opendc-experiments/opendc-experiments-greenifier/src/main/kotlin/org/opendc/experiments/greenifier/topology/ClusterSpec.kt new file mode 100644 index 00000000..905a9ac9 --- /dev/null +++ b/opendc-experiments/opendc-experiments-greenifier/src/main/kotlin/org/opendc/experiments/greenifier/topology/ClusterSpec.kt @@ -0,0 +1,46 @@ +/* + * Copyright (c) 2021 AtLarge Research + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ + +package org.opendc.experiments.greenifier.topology + +/** + * Definition of a compute cluster modeled in the simulation. + * + * @param id A unique identifier representing the compute cluster. + * @param name The name of the cluster. + * @param cpuCount The total number of CPUs in the cluster. + * @param cpuSpeed The speed of a CPU in the cluster in MHz. + * @param memCapacity The total memory capacity of the cluster (in MiB). + * @param hostCount The number of hosts in the cluster. + * @param memCapacityPerHost The memory capacity per host in the cluster (MiB). + * @param cpuCountPerHost The number of CPUs per host in the cluster. + */ +public data class ClusterSpec( + val id: String, + val name: String, + val cpuCount: Int, + val cpuSpeed: Double, + val memCapacity: Double, + val hostCount: Int, + val memCapacityPerHost: Double, + val cpuCountPerHost: Int +) diff --git a/opendc-experiments/opendc-experiments-greenifier/src/main/kotlin/org/opendc/experiments/greenifier/topology/ClusterSpecReader.kt b/opendc-experiments/opendc-experiments-greenifier/src/main/kotlin/org/opendc/experiments/greenifier/topology/ClusterSpecReader.kt new file mode 100644 index 00000000..2488a539 --- /dev/null +++ b/opendc-experiments/opendc-experiments-greenifier/src/main/kotlin/org/opendc/experiments/greenifier/topology/ClusterSpecReader.kt @@ -0,0 +1,121 @@ +/* + * Copyright (c) 2021 AtLarge Research + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ + +package org.opendc.experiments.greenifier.topology + +import com.fasterxml.jackson.annotation.JsonProperty +import com.fasterxml.jackson.databind.MappingIterator +import com.fasterxml.jackson.databind.ObjectReader +import com.fasterxml.jackson.dataformat.csv.CsvMapper +import com.fasterxml.jackson.dataformat.csv.CsvSchema +import java.io.File +import java.io.InputStream + +/** + * A helper class for reading a cluster specification file. + */ +class ClusterSpecReader { + /** + * The [CsvMapper] to map the environment file to an object. + */ + private val mapper = CsvMapper() + + /** + * The [ObjectReader] to convert the lines into objects. + */ + private val reader: ObjectReader = mapper.readerFor(Entry::class.java).with(schema) + + /** + * Read the specified [file]. + */ + fun read(file: File): List<ClusterSpec> { + return reader.readValues<Entry>(file).use { read(it) } + } + + /** + * Read the specified [input]. + */ + fun read(input: InputStream): List<ClusterSpec> { + return reader.readValues<Entry>(input).use { read(it) } + } + + /** + * Convert the specified [MappingIterator] into a list of [ClusterSpec]s. + */ + private fun read(it: MappingIterator<Entry>): List<ClusterSpec> { + val result = mutableListOf<ClusterSpec>() + + for (entry in it) { + val def = ClusterSpec( + entry.id, + entry.name, + entry.cpuCount, + entry.cpuSpeed * 1000, // Convert to MHz + entry.memCapacity * 1000, // Convert to MiB + entry.hostCount, + entry.memCapacityPerHost * 1000, + entry.cpuCountPerHost + ) + result.add(def) + } + + return result + } + + private open class Entry( + @JsonProperty("ClusterID") + val id: String, + @JsonProperty("ClusterName") + val name: String, + @JsonProperty("Cores") + val cpuCount: Int, + @JsonProperty("Speed") + val cpuSpeed: Double, + @JsonProperty("Memory") + val memCapacity: Double, + @JsonProperty("numberOfHosts") + val hostCount: Int, + @JsonProperty("memoryCapacityPerHost") + val memCapacityPerHost: Double, + @JsonProperty("coreCountPerHost") + val cpuCountPerHost: Int + ) + + companion object { + /** + * The [CsvSchema] that is used to parse the trace. + */ + private val schema = CsvSchema.builder() + .addColumn("ClusterID", CsvSchema.ColumnType.STRING) + .addColumn("ClusterName", CsvSchema.ColumnType.STRING) + .addColumn("Cores", CsvSchema.ColumnType.NUMBER) + .addColumn("Speed", CsvSchema.ColumnType.NUMBER) + .addColumn("Memory", CsvSchema.ColumnType.NUMBER) + .addColumn("numberOfHosts", CsvSchema.ColumnType.NUMBER) + .addColumn("memoryCapacityPerHost", CsvSchema.ColumnType.NUMBER) + .addColumn("coreCountPerHost", CsvSchema.ColumnType.NUMBER) + .setAllowComments(true) + .setColumnSeparator(';') + .setUseHeader(true) + .build() + } +} diff --git a/opendc-experiments/opendc-experiments-greenifier/src/main/kotlin/org/opendc/experiments/greenifier/topology/TopologyFactories.kt b/opendc-experiments/opendc-experiments-greenifier/src/main/kotlin/org/opendc/experiments/greenifier/topology/TopologyFactories.kt new file mode 100644 index 00000000..a23ae1a0 --- /dev/null +++ b/opendc-experiments/opendc-experiments-greenifier/src/main/kotlin/org/opendc/experiments/greenifier/topology/TopologyFactories.kt @@ -0,0 +1,99 @@ +/* + * Copyright (c) 2021 AtLarge Research + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ + +@file:JvmName("TopologyFactories") + +package org.opendc.experiments.greenifier.topology + +import org.opendc.experiments.compute.topology.HostSpec +import org.opendc.simulator.compute.SimPsuFactories +import org.opendc.simulator.compute.model.MachineModel +import org.opendc.simulator.compute.model.MemoryUnit +import org.opendc.simulator.compute.model.ProcessingNode +import org.opendc.simulator.compute.model.ProcessingUnit +import org.opendc.simulator.compute.power.CpuPowerModel +import org.opendc.simulator.compute.power.CpuPowerModels +import java.io.File +import java.io.InputStream +import java.util.SplittableRandom +import java.util.UUID +import java.util.random.RandomGenerator +import kotlin.math.roundToLong + +/** + * A [ClusterSpecReader] that is used to read the cluster definition file. + */ +private val reader = ClusterSpecReader() + +/** + * Construct a topology from the specified [file]. + */ +fun clusterTopology( + file: File, + powerModel: CpuPowerModel = CpuPowerModels.linear(350.0, 200.0), + random: RandomGenerator = SplittableRandom(0) +): List<HostSpec> { + return clusterTopology(reader.read(file), powerModel, random) +} + +/** + * Construct a topology from the specified [input]. + */ +fun clusterTopology( + input: InputStream, + powerModel: CpuPowerModel = CpuPowerModels.linear(350.0, 200.0), + random: RandomGenerator = SplittableRandom(0) +): List<HostSpec> { + return clusterTopology(reader.read(input), powerModel, random) +} + +/** + * Construct a topology from the given list of [clusters]. + */ +fun clusterTopology(clusters: List<ClusterSpec>, powerModel: CpuPowerModel, random: RandomGenerator = SplittableRandom(0)): List<HostSpec> { + return clusters.flatMap { it.toHostSpecs(random, powerModel) } +} + +/** + * Helper method to convert a [ClusterSpec] into a list of [HostSpec]s. + */ +private fun ClusterSpec.toHostSpecs(random: RandomGenerator, powerModel: CpuPowerModel): List<HostSpec> { + val cpuSpeed = cpuSpeed + val memoryPerHost = memCapacityPerHost.roundToLong() + + val unknownProcessingNode = ProcessingNode("unknown", "unknown", "unknown", cpuCountPerHost) + val unknownMemoryUnit = MemoryUnit("unknown", "unknown", -1.0, memoryPerHost) + val machineModel = MachineModel( + List(cpuCountPerHost) { coreId -> ProcessingUnit(unknownProcessingNode, coreId, cpuSpeed) }, + listOf(unknownMemoryUnit) + ) + + return List(hostCount) { + HostSpec( + UUID(random.nextLong(), it.toLong()), + "node-$name-$it", + mapOf("cluster" to id), + machineModel, + SimPsuFactories.simple(powerModel) + ) + } +} diff --git a/opendc-experiments/opendc-experiments-greenifier/src/main/resources/bitbrains-small/interference-model.json b/opendc-experiments/opendc-experiments-greenifier/src/main/resources/bitbrains-small/interference-model.json new file mode 100644 index 00000000..51fc6366 --- /dev/null +++ b/opendc-experiments/opendc-experiments-greenifier/src/main/resources/bitbrains-small/interference-model.json @@ -0,0 +1,21 @@ +[ + { + "vms": [ + "141", + "379", + "851", + "116" + ], + "minServerLoad": 0.0, + "performanceScore": 0.8830158730158756 + }, + { + "vms": [ + "205", + "116", + "463" + ], + "minServerLoad": 0.0, + "performanceScore": 0.7133055555552751 + } +] diff --git a/opendc-experiments/opendc-experiments-greenifier/src/main/resources/bitbrains-small/trace/meta.parquet b/opendc-experiments/opendc-experiments-greenifier/src/main/resources/bitbrains-small/trace/meta.parquet Binary files differnew file mode 100644 index 00000000..9cded35f --- /dev/null +++ b/opendc-experiments/opendc-experiments-greenifier/src/main/resources/bitbrains-small/trace/meta.parquet diff --git a/opendc-experiments/opendc-experiments-greenifier/src/main/resources/bitbrains-small/trace/trace.parquet b/opendc-experiments/opendc-experiments-greenifier/src/main/resources/bitbrains-small/trace/trace.parquet Binary files differnew file mode 100644 index 00000000..9d953956 --- /dev/null +++ b/opendc-experiments/opendc-experiments-greenifier/src/main/resources/bitbrains-small/trace/trace.parquet diff --git a/opendc-experiments/opendc-experiments-greenifier/src/main/resources/env/multi.txt b/opendc-experiments/opendc-experiments-greenifier/src/main/resources/env/multi.txt new file mode 100644 index 00000000..6b347bff --- /dev/null +++ b/opendc-experiments/opendc-experiments-greenifier/src/main/resources/env/multi.txt @@ -0,0 +1,5 @@ +ClusterID;ClusterName;Cores;Speed;Memory;numberOfHosts;memoryCapacityPerHost;coreCountPerHost +A01;A01;32;3.2;2048;1;256;32 +B01;B01;48;2.93;1256;6;64;8 +C01;C01;32;3.2;2048;2;128;16 + diff --git a/opendc-experiments/opendc-experiments-greenifier/src/main/resources/env/single.txt b/opendc-experiments/opendc-experiments-greenifier/src/main/resources/env/single.txt new file mode 100644 index 00000000..5642003d --- /dev/null +++ b/opendc-experiments/opendc-experiments-greenifier/src/main/resources/env/single.txt @@ -0,0 +1,3 @@ +ClusterID;ClusterName;Cores;Speed;Memory;numberOfHosts;memoryCapacityPerHost;coreCountPerHost +A01;A01;8;3.2;128;1;128;8 + diff --git a/opendc-experiments/opendc-experiments-greenifier/src/main/resources/log4j2.xml b/opendc-experiments/opendc-experiments-greenifier/src/main/resources/log4j2.xml new file mode 100644 index 00000000..e479f2ca --- /dev/null +++ b/opendc-experiments/opendc-experiments-greenifier/src/main/resources/log4j2.xml @@ -0,0 +1,43 @@ +<?xml version="1.0" encoding="UTF-8"?> +<!-- + ~ MIT License + ~ + ~ Copyright (c) 2020 atlarge-research + ~ + ~ Permission is hereby granted, free of charge, to any person obtaining a copy + ~ of this software and associated documentation files (the "Software"), to deal + ~ in the Software without restriction, including without limitation the rights + ~ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + ~ copies of the Software, and to permit persons to whom the Software is + ~ furnished to do so, subject to the following conditions: + ~ + ~ The above copyright notice and this permission notice shall be included in all + ~ copies or substantial portions of the Software. + ~ + ~ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + ~ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + ~ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + ~ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + ~ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + ~ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + ~ SOFTWARE. + --> + +<Configuration status="WARN"> + <Appenders> + <Console name="Console" target="SYSTEM_OUT"> + <PatternLayout pattern="%d{HH:mm:ss.SSS} [%highlight{%-5level}] %logger{36} - %msg%n" disableAnsi="false"/> + </Console> + </Appenders> + <Loggers> + <Logger name="org.opendc" level="warn" additivity="false"> + <AppenderRef ref="Console"/> + </Logger> + <Logger name="org.apache.hadoop" level="warn" additivity="false"> + <AppenderRef ref="Console"/> + </Logger> + <Root level="error"> + <AppenderRef ref="Console"/> + </Root> + </Loggers> +</Configuration> diff --git a/opendc-experiments/opendc-experiments-greenifier/src/test/kotlin/org/opendc/experiments/capelin/GreenifierIntegrationTest.kt b/opendc-experiments/opendc-experiments-greenifier/src/test/kotlin/org/opendc/experiments/capelin/GreenifierIntegrationTest.kt new file mode 100644 index 00000000..5431a061 --- /dev/null +++ b/opendc-experiments/opendc-experiments-greenifier/src/test/kotlin/org/opendc/experiments/capelin/GreenifierIntegrationTest.kt @@ -0,0 +1,287 @@ +/* + * Copyright (c) 2020 AtLarge Research + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ + +package org.opendc.experiments.greenifier + +import org.junit.jupiter.api.Assertions.assertEquals +import org.junit.jupiter.api.BeforeEach +import org.junit.jupiter.api.Test +import org.junit.jupiter.api.assertAll +import org.opendc.compute.service.ComputeService +import org.opendc.compute.service.scheduler.FilterScheduler +import org.opendc.compute.service.scheduler.filters.ComputeFilter +import org.opendc.compute.service.scheduler.filters.RamFilter +import org.opendc.compute.service.scheduler.filters.VCpuFilter +import org.opendc.compute.service.scheduler.weights.CoreRamWeigher +import org.opendc.experiments.greenifier.topology.clusterTopology +import org.opendc.experiments.compute.ComputeWorkloadLoader +import org.opendc.experiments.compute.VirtualMachine +import org.opendc.experiments.compute.grid5000 +import org.opendc.experiments.compute.registerComputeMonitor +import org.opendc.experiments.compute.replay +import org.opendc.experiments.compute.sampleByLoad +import org.opendc.experiments.compute.setupComputeService +import org.opendc.experiments.compute.setupHosts +import org.opendc.experiments.compute.telemetry.ComputeMonitor +import org.opendc.experiments.compute.telemetry.table.HostTableReader +import org.opendc.experiments.compute.telemetry.table.ServiceTableReader +import org.opendc.experiments.compute.topology.HostSpec +import org.opendc.experiments.compute.trace +import org.opendc.experiments.provisioner.Provisioner +import org.opendc.simulator.kotlin.runSimulation +import java.io.File +import java.time.Duration +import java.util.Random + +/** + * An integration test suite for the Greenifier experiments. + */ +class GreenifierIntegrationTest { + /** + * The monitor used to keep track of the metrics. + */ + private lateinit var monitor: TestComputeMonitor + + /** + * The [FilterScheduler] to use for all experiments. + */ + private lateinit var computeScheduler: FilterScheduler + + /** + * The [ComputeWorkloadLoader] responsible for loading the traces. + */ + private lateinit var workloadLoader: ComputeWorkloadLoader + + /** + * Set up the experimental environment. + */ + @BeforeEach + fun setUp() { + monitor = TestComputeMonitor() + computeScheduler = FilterScheduler( + filters = listOf(ComputeFilter(), VCpuFilter(16.0), RamFilter(1.0)), + weighers = listOf(CoreRamWeigher(multiplier = 1.0)) + ) + workloadLoader = ComputeWorkloadLoader(File("src/test/resources/trace")) + } + + /** + * Test a large simulation setup. + */ + @Test + fun testLarge() = runSimulation { + val seed = 0L + val workload = createTestWorkload(1.0, seed) + val topology = createTopology() + val monitor = monitor + + Provisioner(dispatcher, seed).use { provisioner -> + provisioner.runSteps( + setupComputeService(serviceDomain = "compute.opendc.org", { computeScheduler }), + registerComputeMonitor(serviceDomain = "compute.opendc.org", monitor), + setupHosts(serviceDomain = "compute.opendc.org", topology) + ) + + val service = provisioner.registry.resolve("compute.opendc.org", ComputeService::class.java)!! + service.replay(timeSource, workload, seed) + } + + println( + "Scheduler " + + "Success=${monitor.attemptsSuccess} " + + "Failure=${monitor.attemptsFailure} " + + "Error=${monitor.attemptsError} " + + "Pending=${monitor.serversPending} " + + "Active=${monitor.serversActive}" + ) + + // Note that these values have been verified beforehand + assertAll( + { assertEquals(50, monitor.attemptsSuccess, "The scheduler should schedule 50 VMs") }, + { assertEquals(0, monitor.serversActive, "All VMs should finish after a run") }, + { assertEquals(0, monitor.attemptsFailure, "No VM should be unscheduled") }, + { assertEquals(0, monitor.serversPending, "No VM should not be in the queue") }, + { assertEquals(223394101, monitor.idleTime) { "Incorrect idle time" } }, + { assertEquals(66977086, monitor.activeTime) { "Incorrect active time" } }, + { assertEquals(3160276, monitor.stealTime) { "Incorrect steal time" } }, + { assertEquals(0, monitor.lostTime) { "Incorrect lost time" } }, + { assertEquals(5.84093E9, monitor.energyUsage, 1E4) { "Incorrect power draw" } } + ) + } + + /** + * Test a small simulation setup. + */ + @Test + fun testSmall() = runSimulation { + val seed = 1L + val workload = createTestWorkload(0.25, seed) + val topology = createTopology("single") + val monitor = monitor + + Provisioner(dispatcher, seed).use { provisioner -> + provisioner.runSteps( + setupComputeService(serviceDomain = "compute.opendc.org", { computeScheduler }), + registerComputeMonitor(serviceDomain = "compute.opendc.org", monitor), + setupHosts(serviceDomain = "compute.opendc.org", topology) + ) + + val service = provisioner.registry.resolve("compute.opendc.org", ComputeService::class.java)!! + service.replay(timeSource, workload, seed) + } + + println( + "Scheduler " + + "Success=${monitor.attemptsSuccess} " + + "Failure=${monitor.attemptsFailure} " + + "Error=${monitor.attemptsError} " + + "Pending=${monitor.serversPending} " + + "Active=${monitor.serversActive}" + ) + + // Note that these values have been verified beforehand + assertAll( + { assertEquals(10999514, monitor.idleTime) { "Idle time incorrect" } }, + { assertEquals(9741285, monitor.activeTime) { "Active time incorrect" } }, + { assertEquals(0, monitor.stealTime) { "Steal time incorrect" } }, + { assertEquals(0, monitor.lostTime) { "Lost time incorrect" } }, + { assertEquals(7.0116E8, monitor.energyUsage, 1E4) { "Incorrect power draw" } } + ) + } + + /** + * Test a small simulation setup with interference. + */ + @Test + fun testInterference() = runSimulation { + val seed = 0L + val workload = createTestWorkload(1.0, seed) + val topology = createTopology("single") + + Provisioner(dispatcher, seed).use { provisioner -> + provisioner.runSteps( + setupComputeService(serviceDomain = "compute.opendc.org", { computeScheduler }), + registerComputeMonitor(serviceDomain = "compute.opendc.org", monitor), + setupHosts(serviceDomain = "compute.opendc.org", topology) + ) + + val service = provisioner.registry.resolve("compute.opendc.org", ComputeService::class.java)!! + service.replay(timeSource, workload, seed, interference = true) + } + + println( + "Scheduler " + + "Success=${monitor.attemptsSuccess} " + + "Failure=${monitor.attemptsFailure} " + + "Error=${monitor.attemptsError} " + + "Pending=${monitor.serversPending} " + + "Active=${monitor.serversActive}" + ) + + // Note that these values have been verified beforehand + assertAll( + { assertEquals(6028018, monitor.idleTime) { "Idle time incorrect" } }, + { assertEquals(14712781, monitor.activeTime) { "Active time incorrect" } }, + { assertEquals(12532934, monitor.stealTime) { "Steal time incorrect" } }, + { assertEquals(424267, monitor.lostTime) { "Lost time incorrect" } } + ) + } + + /** + * Test a small simulation setup with failures. + */ + @Test + fun testFailures() = runSimulation { + val seed = 0L + val topology = createTopology("single") + val workload = createTestWorkload(0.25, seed) + val monitor = monitor + + Provisioner(dispatcher, seed).use { provisioner -> + provisioner.runSteps( + setupComputeService(serviceDomain = "compute.opendc.org", { computeScheduler }), + registerComputeMonitor(serviceDomain = "compute.opendc.org", monitor), + setupHosts(serviceDomain = "compute.opendc.org", topology) + ) + + val service = provisioner.registry.resolve("compute.opendc.org", ComputeService::class.java)!! + service.replay(timeSource, workload, seed, failureModel = grid5000(Duration.ofDays(7))) + } + + // Note that these values have been verified beforehand + assertAll( + { assertEquals(10085111, monitor.idleTime) { "Idle time incorrect" } }, + { assertEquals(8539204, monitor.activeTime) { "Active time incorrect" } }, + { assertEquals(0, monitor.stealTime) { "Steal time incorrect" } }, + { assertEquals(0, monitor.lostTime) { "Lost time incorrect" } }, + { assertEquals(2328039558, monitor.uptime) { "Uptime incorrect" } } + ) + } + + /** + * Obtain the trace reader for the test. + */ + private fun createTestWorkload(fraction: Double, seed: Long): List<VirtualMachine> { + val source = trace("bitbrains-small").sampleByLoad(fraction) + return source.resolve(workloadLoader, Random(seed)) + } + + /** + * Obtain the topology factory for the test. + */ + private fun createTopology(name: String = "topology"): List<HostSpec> { + val stream = checkNotNull(object {}.javaClass.getResourceAsStream("/env/$name.txt")) + return stream.use { clusterTopology(stream) } + } + + class TestComputeMonitor : ComputeMonitor { + var attemptsSuccess = 0 + var attemptsFailure = 0 + var attemptsError = 0 + var serversPending = 0 + var serversActive = 0 + + override fun record(reader: ServiceTableReader) { + attemptsSuccess = reader.attemptsSuccess + attemptsFailure = reader.attemptsFailure + attemptsError = reader.attemptsError + serversPending = reader.serversPending + serversActive = reader.serversActive + } + + var idleTime = 0L + var activeTime = 0L + var stealTime = 0L + var lostTime = 0L + var energyUsage = 0.0 + var uptime = 0L + + override fun record(reader: HostTableReader) { + idleTime += reader.cpuIdleTime + activeTime += reader.cpuActiveTime + stealTime += reader.cpuStealTime + lostTime += reader.cpuLostTime + energyUsage += reader.powerTotal + uptime += reader.uptime + } + } +} diff --git a/opendc-experiments/opendc-experiments-greenifier/src/test/kotlin/org/opendc/experiments/capelin/GreenifierRunnerTest.kt b/opendc-experiments/opendc-experiments-greenifier/src/test/kotlin/org/opendc/experiments/capelin/GreenifierRunnerTest.kt new file mode 100644 index 00000000..b6e361d7 --- /dev/null +++ b/opendc-experiments/opendc-experiments-greenifier/src/test/kotlin/org/opendc/experiments/capelin/GreenifierRunnerTest.kt @@ -0,0 +1,86 @@ +/* + * Copyright (c) 2022 AtLarge Research + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ + +package org.opendc.experiments.greenifier + +import org.junit.jupiter.api.Test +import org.junit.jupiter.api.assertDoesNotThrow +import org.opendc.experiments.greenifier.model.OperationalPhenomena +import org.opendc.experiments.greenifier.model.Scenario +import org.opendc.experiments.greenifier.model.Topology +import org.opendc.experiments.greenifier.model.Workload +import org.opendc.experiments.compute.trace +import java.io.File +import java.nio.file.Files + +/** + * Test suite for [GreenifierRunner]. + */ +class GreenifierRunnerTest { + /** + * The path to the environments. + */ + private val envPath = File("src/test/resources/env") + + /** + * The path to the traces. + */ + private val tracePath = File("src/test/resources/trace") + + /** + * Smoke test with output. + */ + @Test + fun testSmoke() { + val outputPath = Files.createTempDirectory("output").toFile() + + try { + val runner = GreenifierRunner(envPath, tracePath, outputPath) + val scenario = Scenario( + Topology("topology"), + Workload("bitbrains-small", trace("bitbrains-small")), + OperationalPhenomena(failureFrequency = 24.0 * 7, hasInterference = true), + "active-servers" + ) + + assertDoesNotThrow { runner.runScenario(scenario, seed = 0L) } + } finally { + outputPath.delete() + } + } + + /** + * Smoke test without output. + */ + @Test + fun testSmokeNoOutput() { + val runner = GreenifierRunner(envPath, tracePath, null) + val scenario = Scenario( + Topology("topology"), + Workload("bitbrains-small", trace("bitbrains-small")), + OperationalPhenomena(failureFrequency = 24.0 * 7, hasInterference = true), + "active-servers" + ) + + assertDoesNotThrow { runner.runScenario(scenario, seed = 0L) } + } +} diff --git a/opendc-experiments/opendc-experiments-greenifier/src/test/resources/env/single.txt b/opendc-experiments/opendc-experiments-greenifier/src/test/resources/env/single.txt new file mode 100644 index 00000000..5642003d --- /dev/null +++ b/opendc-experiments/opendc-experiments-greenifier/src/test/resources/env/single.txt @@ -0,0 +1,3 @@ +ClusterID;ClusterName;Cores;Speed;Memory;numberOfHosts;memoryCapacityPerHost;coreCountPerHost +A01;A01;8;3.2;128;1;128;8 + diff --git a/opendc-experiments/opendc-experiments-greenifier/src/test/resources/env/topology.txt b/opendc-experiments/opendc-experiments-greenifier/src/test/resources/env/topology.txt new file mode 100644 index 00000000..6b347bff --- /dev/null +++ b/opendc-experiments/opendc-experiments-greenifier/src/test/resources/env/topology.txt @@ -0,0 +1,5 @@ +ClusterID;ClusterName;Cores;Speed;Memory;numberOfHosts;memoryCapacityPerHost;coreCountPerHost +A01;A01;32;3.2;2048;1;256;32 +B01;B01;48;2.93;1256;6;64;8 +C01;C01;32;3.2;2048;2;128;16 + diff --git a/opendc-experiments/opendc-experiments-greenifier/src/test/resources/trace/bitbrains-small/interference-model.json b/opendc-experiments/opendc-experiments-greenifier/src/test/resources/trace/bitbrains-small/interference-model.json new file mode 100644 index 00000000..51fc6366 --- /dev/null +++ b/opendc-experiments/opendc-experiments-greenifier/src/test/resources/trace/bitbrains-small/interference-model.json @@ -0,0 +1,21 @@ +[ + { + "vms": [ + "141", + "379", + "851", + "116" + ], + "minServerLoad": 0.0, + "performanceScore": 0.8830158730158756 + }, + { + "vms": [ + "205", + "116", + "463" + ], + "minServerLoad": 0.0, + "performanceScore": 0.7133055555552751 + } +] diff --git a/opendc-experiments/opendc-experiments-greenifier/src/test/resources/trace/bitbrains-small/meta.parquet b/opendc-experiments/opendc-experiments-greenifier/src/test/resources/trace/bitbrains-small/meta.parquet Binary files differnew file mode 100644 index 00000000..9cded35f --- /dev/null +++ b/opendc-experiments/opendc-experiments-greenifier/src/test/resources/trace/bitbrains-small/meta.parquet diff --git a/opendc-experiments/opendc-experiments-greenifier/src/test/resources/trace/bitbrains-small/trace.parquet b/opendc-experiments/opendc-experiments-greenifier/src/test/resources/trace/bitbrains-small/trace.parquet Binary files differnew file mode 100644 index 00000000..9d953956 --- /dev/null +++ b/opendc-experiments/opendc-experiments-greenifier/src/test/resources/trace/bitbrains-small/trace.parquet diff --git a/output/host/topology=multi/workload=bitbrains-small/seed=0/data.parquet b/output/host/topology=multi/workload=bitbrains-small/seed=0/data.parquet Binary files differnew file mode 100644 index 00000000..df9116a6 --- /dev/null +++ b/output/host/topology=multi/workload=bitbrains-small/seed=0/data.parquet diff --git a/output/host/topology=single/workload=bitbrains-small/seed=0/data.parquet b/output/host/topology=single/workload=bitbrains-small/seed=0/data.parquet Binary files differnew file mode 100644 index 00000000..fb9e1acd --- /dev/null +++ b/output/host/topology=single/workload=bitbrains-small/seed=0/data.parquet diff --git a/output/server/topology=multi/workload=bitbrains-small/seed=0/data.parquet b/output/server/topology=multi/workload=bitbrains-small/seed=0/data.parquet Binary files differnew file mode 100644 index 00000000..c87b269a --- /dev/null +++ b/output/server/topology=multi/workload=bitbrains-small/seed=0/data.parquet diff --git a/output/server/topology=single/workload=bitbrains-small/seed=0/data.parquet b/output/server/topology=single/workload=bitbrains-small/seed=0/data.parquet Binary files differnew file mode 100644 index 00000000..9f85d9b7 --- /dev/null +++ b/output/server/topology=single/workload=bitbrains-small/seed=0/data.parquet diff --git a/output/service/topology=multi/workload=bitbrains-small/seed=0/data.parquet b/output/service/topology=multi/workload=bitbrains-small/seed=0/data.parquet Binary files differnew file mode 100644 index 00000000..2e2e6851 --- /dev/null +++ b/output/service/topology=multi/workload=bitbrains-small/seed=0/data.parquet diff --git a/output/service/topology=single/workload=bitbrains-small/seed=0/data.parquet b/output/service/topology=single/workload=bitbrains-small/seed=0/data.parquet Binary files differnew file mode 100644 index 00000000..7e5974a3 --- /dev/null +++ b/output/service/topology=single/workload=bitbrains-small/seed=0/data.parquet diff --git a/resources/bitbrains-small/interference-model.json b/resources/bitbrains-small/interference-model.json new file mode 100644 index 00000000..51fc6366 --- /dev/null +++ b/resources/bitbrains-small/interference-model.json @@ -0,0 +1,21 @@ +[ + { + "vms": [ + "141", + "379", + "851", + "116" + ], + "minServerLoad": 0.0, + "performanceScore": 0.8830158730158756 + }, + { + "vms": [ + "205", + "116", + "463" + ], + "minServerLoad": 0.0, + "performanceScore": 0.7133055555552751 + } +] diff --git a/resources/bitbrains-small/trace/meta.parquet b/resources/bitbrains-small/trace/meta.parquet Binary files differnew file mode 100644 index 00000000..9cded35f --- /dev/null +++ b/resources/bitbrains-small/trace/meta.parquet diff --git a/resources/bitbrains-small/trace/trace.parquet b/resources/bitbrains-small/trace/trace.parquet Binary files differnew file mode 100644 index 00000000..9d953956 --- /dev/null +++ b/resources/bitbrains-small/trace/trace.parquet diff --git a/resources/env/multi.txt b/resources/env/multi.txt new file mode 100644 index 00000000..6b347bff --- /dev/null +++ b/resources/env/multi.txt @@ -0,0 +1,5 @@ +ClusterID;ClusterName;Cores;Speed;Memory;numberOfHosts;memoryCapacityPerHost;coreCountPerHost +A01;A01;32;3.2;2048;1;256;32 +B01;B01;48;2.93;1256;6;64;8 +C01;C01;32;3.2;2048;2;128;16 + diff --git a/resources/env/single.txt b/resources/env/single.txt new file mode 100644 index 00000000..5642003d --- /dev/null +++ b/resources/env/single.txt @@ -0,0 +1,3 @@ +ClusterID;ClusterName;Cores;Speed;Memory;numberOfHosts;memoryCapacityPerHost;coreCountPerHost +A01;A01;8;3.2;128;1;128;8 + diff --git a/resources/log4j2.xml b/resources/log4j2.xml new file mode 100644 index 00000000..e479f2ca --- /dev/null +++ b/resources/log4j2.xml @@ -0,0 +1,43 @@ +<?xml version="1.0" encoding="UTF-8"?> +<!-- + ~ MIT License + ~ + ~ Copyright (c) 2020 atlarge-research + ~ + ~ Permission is hereby granted, free of charge, to any person obtaining a copy + ~ of this software and associated documentation files (the "Software"), to deal + ~ in the Software without restriction, including without limitation the rights + ~ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + ~ copies of the Software, and to permit persons to whom the Software is + ~ furnished to do so, subject to the following conditions: + ~ + ~ The above copyright notice and this permission notice shall be included in all + ~ copies or substantial portions of the Software. + ~ + ~ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + ~ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + ~ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + ~ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + ~ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + ~ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + ~ SOFTWARE. + --> + +<Configuration status="WARN"> + <Appenders> + <Console name="Console" target="SYSTEM_OUT"> + <PatternLayout pattern="%d{HH:mm:ss.SSS} [%highlight{%-5level}] %logger{36} - %msg%n" disableAnsi="false"/> + </Console> + </Appenders> + <Loggers> + <Logger name="org.opendc" level="warn" additivity="false"> + <AppenderRef ref="Console"/> + </Logger> + <Logger name="org.apache.hadoop" level="warn" additivity="false"> + <AppenderRef ref="Console"/> + </Logger> + <Root level="error"> + <AppenderRef ref="Console"/> + </Root> + </Loggers> +</Configuration> diff --git a/settings.gradle.kts b/settings.gradle.kts index c824f537..aaf48521 100644 --- a/settings.gradle.kts +++ b/settings.gradle.kts @@ -35,6 +35,7 @@ include(":opendc-experiments:opendc-experiments-compute") include(":opendc-experiments:opendc-experiments-workflow") include(":opendc-experiments:opendc-experiments-faas") include(":opendc-experiments:opendc-experiments-capelin") +include(":opendc-experiments:opendc-experiments-greenifier") include(":opendc-experiments:opendc-experiments-tf20") include(":opendc-web:opendc-web-proto") include(":opendc-web:opendc-web-server") @@ -64,3 +65,5 @@ include(":opendc-trace:opendc-trace-calcite") include(":opendc-trace:opendc-trace-tools") enableFeaturePreview("TYPESAFE_PROJECT_ACCESSORS") +include("opendc-experiments:opendc-experiments-greenifier") +findProject(":opendc-experiments:opendc-experiments-greenifier")?.name = "opendc-experiments-greenifier" |
