From 16178eb0499ad398f14bd209e9c1a6e5b52850a9 Mon Sep 17 00:00:00 2001 From: Fabian Mastenbroek Date: Tue, 23 Mar 2021 14:53:23 +0100 Subject: simulator: Add benchmarks for opendc-simulator-compute This change adds benchmarks to the opendc-simulator-compute module in order to quantify effect of changes on the performance of this module. --- .../opendc-simulator-compute/build.gradle.kts | 3 +- .../opendc/simulator/compute/BenchmarkHelpers.kt | 43 ++++++ .../simulator/compute/SimMachineBenchmarks.kt | 145 +++++++++++++++++++++ 3 files changed, 190 insertions(+), 1 deletion(-) create mode 100644 simulator/opendc-simulator/opendc-simulator-compute/src/jmh/kotlin/org/opendc/simulator/compute/BenchmarkHelpers.kt create mode 100644 simulator/opendc-simulator/opendc-simulator-compute/src/jmh/kotlin/org/opendc/simulator/compute/SimMachineBenchmarks.kt diff --git a/simulator/opendc-simulator/opendc-simulator-compute/build.gradle.kts b/simulator/opendc-simulator/opendc-simulator-compute/build.gradle.kts index 0005928f..149c0ed2 100644 --- a/simulator/opendc-simulator/opendc-simulator-compute/build.gradle.kts +++ b/simulator/opendc-simulator/opendc-simulator-compute/build.gradle.kts @@ -20,12 +20,13 @@ * SOFTWARE. */ -description = "Library for simulation of cloud computing components" +description = "Library for simulating computing workloads" plugins { `kotlin-library-conventions` `testing-conventions` `jacoco-conventions` + `benchmark-conventions` } dependencies { diff --git a/simulator/opendc-simulator/opendc-simulator-compute/src/jmh/kotlin/org/opendc/simulator/compute/BenchmarkHelpers.kt b/simulator/opendc-simulator/opendc-simulator-compute/src/jmh/kotlin/org/opendc/simulator/compute/BenchmarkHelpers.kt new file mode 100644 index 00000000..43bbfd0b --- /dev/null +++ b/simulator/opendc-simulator/opendc-simulator-compute/src/jmh/kotlin/org/opendc/simulator/compute/BenchmarkHelpers.kt @@ -0,0 +1,43 @@ +/* + * 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.simulator.compute + +import org.opendc.simulator.compute.workload.SimTraceWorkload + +/** + * Helper function to create simple consumer workload. + */ +fun createSimpleConsumer(): SimTraceWorkload { + return SimTraceWorkload( + sequenceOf( + SimTraceWorkload.Fragment(1000, 28.0, 1), + SimTraceWorkload.Fragment(1000, 3500.0, 1), + SimTraceWorkload.Fragment(1000, 0.0, 1), + SimTraceWorkload.Fragment(1000, 183.0, 1), + SimTraceWorkload.Fragment(1000, 400.0, 1), + SimTraceWorkload.Fragment(1000, 100.0, 1), + SimTraceWorkload.Fragment(1000, 3000.0, 1), + SimTraceWorkload.Fragment(1000, 4500.0, 1), + ), + ) +} diff --git a/simulator/opendc-simulator/opendc-simulator-compute/src/jmh/kotlin/org/opendc/simulator/compute/SimMachineBenchmarks.kt b/simulator/opendc-simulator/opendc-simulator-compute/src/jmh/kotlin/org/opendc/simulator/compute/SimMachineBenchmarks.kt new file mode 100644 index 00000000..eb22c855 --- /dev/null +++ b/simulator/opendc-simulator/opendc-simulator-compute/src/jmh/kotlin/org/opendc/simulator/compute/SimMachineBenchmarks.kt @@ -0,0 +1,145 @@ +/* + * 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.simulator.compute + +import kotlinx.coroutines.ExperimentalCoroutinesApi +import kotlinx.coroutines.coroutineScope +import kotlinx.coroutines.launch +import kotlinx.coroutines.test.TestCoroutineScope +import kotlinx.coroutines.test.runBlockingTest +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.workload.SimWorkload +import org.opendc.simulator.utils.DelayControllerClockAdapter +import org.opendc.utils.TimerScheduler +import org.openjdk.jmh.annotations.* +import java.time.Clock +import java.util.concurrent.TimeUnit + +@State(Scope.Thread) +@Fork(1) +@Warmup(iterations = 2, time = 1, timeUnit = TimeUnit.SECONDS) +@Measurement(iterations = 5, time = 3, timeUnit = TimeUnit.SECONDS) +@OptIn(ExperimentalCoroutinesApi::class) +class SimMachineBenchmarks { + private lateinit var scope: TestCoroutineScope + private lateinit var clock: Clock + private lateinit var scheduler: TimerScheduler + private lateinit var machineModel: SimMachineModel + + @Setup + fun setUp() { + scope = TestCoroutineScope() + clock = DelayControllerClockAdapter(scope) + scheduler = TimerScheduler(scope.coroutineContext, clock) + + val cpuNode = ProcessingNode("Intel", "Xeon", "amd64", 2) + + machineModel = SimMachineModel( + cpus = List(cpuNode.coreCount) { ProcessingUnit(cpuNode, it, 1000.0) }, + memory = List(4) { MemoryUnit("Crucial", "MTA18ASF4G72AZ-3G2B1", 3200.0, 32_000) } + ) + } + + @State(Scope.Thread) + class Workload { + lateinit var workloads: Array + + @Setup + fun setUp() { + workloads = Array(2) { createSimpleConsumer() } + } + } + + @Benchmark + fun benchmarkBareMetal(state: Workload) { + return scope.runBlockingTest { + val machine = SimBareMetalMachine(scope.coroutineContext, clock, machineModel) + return@runBlockingTest machine.run(state.workloads[0]) + } + } + + @Benchmark + fun benchmarkSpaceSharedHypervisor(state: Workload) { + return scope.runBlockingTest { + val machine = SimBareMetalMachine(coroutineContext, clock, machineModel) + val hypervisor = SimSpaceSharedHypervisor() + + launch { machine.run(hypervisor) } + + val vm = hypervisor.createMachine(machineModel) + + try { + return@runBlockingTest vm.run(state.workloads[0]) + } finally { + vm.close() + machine.close() + } + } + } + + @Benchmark + fun benchmarkFairShareHypervisorSingle(state: Workload) { + return scope.runBlockingTest { + val machine = SimBareMetalMachine(coroutineContext, clock, machineModel) + val hypervisor = SimFairShareHypervisor() + + launch { machine.run(hypervisor) } + + val vm = hypervisor.createMachine(machineModel) + + try { + return@runBlockingTest vm.run(state.workloads[0]) + } finally { + vm.close() + machine.close() + } + } + } + + @Benchmark + fun benchmarkFairShareHypervisorDouble(state: Workload) { + return scope.runBlockingTest { + val machine = SimBareMetalMachine(coroutineContext, clock, machineModel) + val hypervisor = SimFairShareHypervisor() + + launch { machine.run(hypervisor) } + + coroutineScope { + repeat(2) { i -> + val vm = hypervisor.createMachine(machineModel) + + launch { + try { + vm.run(state.workloads[i]) + } finally { + machine.close() + } + } + } + } + machine.close() + } + } +} -- cgit v1.2.3 From 0ad600eb8e14c0ef3ba5529c59d300dc20c85ab2 Mon Sep 17 00:00:00 2001 From: Fabian Mastenbroek Date: Tue, 23 Mar 2021 17:07:31 +0100 Subject: simulator: Add support for transforming resource consumers This change adds support for transforming the resource commands emitted by the resource consumers. The SimResourceForwarder is modified to also support transforming the resource commands. --- .../simulator/compute/SimAbstractHypervisor.kt | 4 +- .../simulator/resources/SimResourceBenchmarks.kt | 2 +- .../opendc/simulator/resources/SimResourceFlow.kt | 29 +++ .../simulator/resources/SimResourceForwarder.kt | 156 --------------- .../resources/SimResourceSwitchExclusive.kt | 4 +- .../simulator/resources/SimResourceTransformer.kt | 171 ++++++++++++++++ .../resources/SimResourceForwarderTest.kt | 186 ------------------ .../resources/SimResourceTransformerTest.kt | 218 +++++++++++++++++++++ 8 files changed, 423 insertions(+), 347 deletions(-) create mode 100644 simulator/opendc-simulator/opendc-simulator-resources/src/main/kotlin/org/opendc/simulator/resources/SimResourceFlow.kt delete mode 100644 simulator/opendc-simulator/opendc-simulator-resources/src/main/kotlin/org/opendc/simulator/resources/SimResourceForwarder.kt create mode 100644 simulator/opendc-simulator/opendc-simulator-resources/src/main/kotlin/org/opendc/simulator/resources/SimResourceTransformer.kt delete mode 100644 simulator/opendc-simulator/opendc-simulator-resources/src/test/kotlin/org/opendc/simulator/resources/SimResourceForwarderTest.kt create mode 100644 simulator/opendc-simulator/opendc-simulator-resources/src/test/kotlin/org/opendc/simulator/resources/SimResourceTransformerTest.kt diff --git a/simulator/opendc-simulator/opendc-simulator-compute/src/main/kotlin/org/opendc/simulator/compute/SimAbstractHypervisor.kt b/simulator/opendc-simulator/opendc-simulator-compute/src/main/kotlin/org/opendc/simulator/compute/SimAbstractHypervisor.kt index 81d09f12..8046dd53 100644 --- a/simulator/opendc-simulator/opendc-simulator-compute/src/main/kotlin/org/opendc/simulator/compute/SimAbstractHypervisor.kt +++ b/simulator/opendc-simulator/opendc-simulator-compute/src/main/kotlin/org/opendc/simulator/compute/SimAbstractHypervisor.kt @@ -142,11 +142,11 @@ public abstract class SimAbstractHypervisor : SimHypervisor { */ override fun close() { if (!isTerminated) { + isTerminated = true + cpus.forEach { (_, provider) -> provider.close() } _vms.remove(this) } - - isTerminated = true } } diff --git a/simulator/opendc-simulator/opendc-simulator-resources/src/jmh/kotlin/org/opendc/simulator/resources/SimResourceBenchmarks.kt b/simulator/opendc-simulator/opendc-simulator-resources/src/jmh/kotlin/org/opendc/simulator/resources/SimResourceBenchmarks.kt index f2eea97c..937b6966 100644 --- a/simulator/opendc-simulator/opendc-simulator-resources/src/jmh/kotlin/org/opendc/simulator/resources/SimResourceBenchmarks.kt +++ b/simulator/opendc-simulator/opendc-simulator-resources/src/jmh/kotlin/org/opendc/simulator/resources/SimResourceBenchmarks.kt @@ -71,7 +71,7 @@ class SimResourceBenchmarks { fun benchmarkForwardOverhead(state: Workload) { return scope.runBlockingTest { val provider = SimResourceSource(4200.0, clock, scheduler) - val forwarder = SimResourceForwarder() + val forwarder = SimResourceTransformer() provider.startConsumer(forwarder) return@runBlockingTest forwarder.consume(state.consumers[0]) } diff --git a/simulator/opendc-simulator/opendc-simulator-resources/src/main/kotlin/org/opendc/simulator/resources/SimResourceFlow.kt b/simulator/opendc-simulator/opendc-simulator-resources/src/main/kotlin/org/opendc/simulator/resources/SimResourceFlow.kt new file mode 100644 index 00000000..bbf6ad44 --- /dev/null +++ b/simulator/opendc-simulator/opendc-simulator-resources/src/main/kotlin/org/opendc/simulator/resources/SimResourceFlow.kt @@ -0,0 +1,29 @@ +/* + * 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.simulator.resources + +/** + * A [SimResourceFlow] acts as both a resource consumer and resource provider at the same time, simplifying bridging + * between different components. + */ +public interface SimResourceFlow : SimResourceConsumer, SimResourceProvider diff --git a/simulator/opendc-simulator/opendc-simulator-resources/src/main/kotlin/org/opendc/simulator/resources/SimResourceForwarder.kt b/simulator/opendc-simulator/opendc-simulator-resources/src/main/kotlin/org/opendc/simulator/resources/SimResourceForwarder.kt deleted file mode 100644 index 1a05accd..00000000 --- a/simulator/opendc-simulator/opendc-simulator-resources/src/main/kotlin/org/opendc/simulator/resources/SimResourceForwarder.kt +++ /dev/null @@ -1,156 +0,0 @@ -/* - * 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.simulator.resources - -/** - * A helper class to construct a [SimResourceProvider] which forwards the requests to a [SimResourceConsumer]. - */ -public class SimResourceForwarder : SimResourceProvider, SimResourceConsumer { - /** - * The [SimResourceContext] in which the forwarder runs. - */ - private var ctx: SimResourceContext? = null - - /** - * The delegate [SimResourceConsumer]. - */ - private var delegate: SimResourceConsumer? = null - - /** - * A flag to indicate that the delegate was started. - */ - private var hasDelegateStarted: Boolean = false - - /** - * The state of the forwarder. - */ - override var state: SimResourceState = SimResourceState.Pending - private set - - override fun startConsumer(consumer: SimResourceConsumer) { - check(state == SimResourceState.Pending) { "Resource is in invalid state" } - - state = SimResourceState.Active - delegate = consumer - - // Interrupt the provider to replace the consumer - interrupt() - } - - override fun interrupt() { - ctx?.interrupt() - } - - override fun cancel() { - val delegate = delegate - val ctx = ctx - - state = SimResourceState.Pending - - if (delegate != null && ctx != null) { - this.delegate = null - delegate.onFinish(ctx) - } - } - - override fun close() { - val ctx = ctx - - state = SimResourceState.Stopped - - if (ctx != null) { - this.ctx = null - ctx.interrupt() - } - } - - override fun onStart(ctx: SimResourceContext) { - this.ctx = ctx - } - - override fun onNext(ctx: SimResourceContext): SimResourceCommand { - val delegate = delegate - - if (!hasDelegateStarted) { - start() - } - - return if (state == SimResourceState.Stopped) { - SimResourceCommand.Exit - } else if (delegate != null) { - val command = delegate.onNext(ctx) - if (command == SimResourceCommand.Exit) { - // Warning: resumption of the continuation might change the entire state of the forwarder. Make sure we - // reset beforehand the existing state and check whether it has been updated afterwards - reset() - - delegate.onFinish(ctx) - - if (state == SimResourceState.Stopped) - SimResourceCommand.Exit - else - onNext(ctx) - } else { - command - } - } else { - SimResourceCommand.Idle() - } - } - - override fun onCapacityChanged(ctx: SimResourceContext, isThrottled: Boolean) { - delegate?.onCapacityChanged(ctx, isThrottled) - } - - override fun onFinish(ctx: SimResourceContext, cause: Throwable?) { - this.ctx = null - - val delegate = delegate - if (delegate != null) { - reset() - delegate.onFinish(ctx, cause) - } - } - - /** - * Start the delegate. - */ - private fun start() { - val delegate = delegate ?: return - delegate.onStart(checkNotNull(ctx)) - - hasDelegateStarted = true - } - - /** - * Reset the delegate. - */ - private fun reset() { - delegate = null - hasDelegateStarted = false - - if (state != SimResourceState.Stopped) { - state = SimResourceState.Pending - } - } -} diff --git a/simulator/opendc-simulator/opendc-simulator-resources/src/main/kotlin/org/opendc/simulator/resources/SimResourceSwitchExclusive.kt b/simulator/opendc-simulator/opendc-simulator-resources/src/main/kotlin/org/opendc/simulator/resources/SimResourceSwitchExclusive.kt index a10f84b6..45e4c220 100644 --- a/simulator/opendc-simulator/opendc-simulator-resources/src/main/kotlin/org/opendc/simulator/resources/SimResourceSwitchExclusive.kt +++ b/simulator/opendc-simulator/opendc-simulator-resources/src/main/kotlin/org/opendc/simulator/resources/SimResourceSwitchExclusive.kt @@ -38,7 +38,7 @@ public class SimResourceSwitchExclusive : SimResourceSwitch { override val outputs: Set get() = _outputs - private val availableResources = ArrayDeque() + private val availableResources = ArrayDeque() private val _inputs = mutableSetOf() override val inputs: Set @@ -83,7 +83,7 @@ public class SimResourceSwitchExclusive : SimResourceSwitch { private inner class Provider( private val capacity: Double, - private val forwarder: SimResourceForwarder + private val forwarder: SimResourceTransformer ) : SimResourceProvider by forwarder { override fun close() { // We explicitly do not close the forwarder here in order to re-use it across output resources. diff --git a/simulator/opendc-simulator/opendc-simulator-resources/src/main/kotlin/org/opendc/simulator/resources/SimResourceTransformer.kt b/simulator/opendc-simulator/opendc-simulator-resources/src/main/kotlin/org/opendc/simulator/resources/SimResourceTransformer.kt new file mode 100644 index 00000000..73f18c7c --- /dev/null +++ b/simulator/opendc-simulator/opendc-simulator-resources/src/main/kotlin/org/opendc/simulator/resources/SimResourceTransformer.kt @@ -0,0 +1,171 @@ +/* + * 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.simulator.resources + +/** + * A [SimResourceFlow] that transforms the resource commands emitted by the resource commands to the resource provider. + * + * @param isCoupled A flag to indicate that the transformer will exit when the resource consumer exits. + * @param transform The function to transform the received resource command. + */ +public class SimResourceTransformer( + private val isCoupled: Boolean = false, + private val transform: (SimResourceContext, SimResourceCommand) -> SimResourceCommand +) : SimResourceFlow { + /** + * The [SimResourceContext] in which the forwarder runs. + */ + private var ctx: SimResourceContext? = null + + /** + * The delegate [SimResourceConsumer]. + */ + private var delegate: SimResourceConsumer? = null + + /** + * A flag to indicate that the delegate was started. + */ + private var hasDelegateStarted: Boolean = false + + /** + * The state of the forwarder. + */ + override var state: SimResourceState = SimResourceState.Pending + private set + + override fun startConsumer(consumer: SimResourceConsumer) { + check(state == SimResourceState.Pending) { "Resource is in invalid state" } + + state = SimResourceState.Active + delegate = consumer + + // Interrupt the provider to replace the consumer + interrupt() + } + + override fun interrupt() { + ctx?.interrupt() + } + + override fun cancel() { + val delegate = delegate + val ctx = ctx + + state = SimResourceState.Pending + + if (delegate != null && ctx != null) { + this.delegate = null + delegate.onFinish(ctx) + } + } + + override fun close() { + val ctx = ctx + + state = SimResourceState.Stopped + + if (ctx != null) { + this.ctx = null + ctx.interrupt() + } + } + + override fun onStart(ctx: SimResourceContext) { + this.ctx = ctx + } + + override fun onNext(ctx: SimResourceContext): SimResourceCommand { + val delegate = delegate + + if (!hasDelegateStarted) { + start() + } + + return if (state == SimResourceState.Stopped) { + SimResourceCommand.Exit + } else if (delegate != null) { + val command = transform(ctx, delegate.onNext(ctx)) + if (command == SimResourceCommand.Exit) { + // Warning: resumption of the continuation might change the entire state of the forwarder. Make sure we + // reset beforehand the existing state and check whether it has been updated afterwards + reset() + + delegate.onFinish(ctx) + + if (isCoupled || state == SimResourceState.Stopped) + SimResourceCommand.Exit + else + onNext(ctx) + } else { + command + } + } else { + SimResourceCommand.Idle() + } + } + + override fun onCapacityChanged(ctx: SimResourceContext, isThrottled: Boolean) { + delegate?.onCapacityChanged(ctx, isThrottled) + } + + override fun onFinish(ctx: SimResourceContext, cause: Throwable?) { + this.ctx = null + + val delegate = delegate + if (delegate != null) { + reset() + delegate.onFinish(ctx, cause) + } + } + + /** + * Start the delegate. + */ + private fun start() { + val delegate = delegate ?: return + delegate.onStart(checkNotNull(ctx)) + + hasDelegateStarted = true + } + + /** + * Reset the delegate. + */ + private fun reset() { + delegate = null + hasDelegateStarted = false + + if (state != SimResourceState.Stopped) { + state = SimResourceState.Pending + } + } +} + +/** + * Constructs a [SimResourceTransformer] that forwards the received resource command with an identity transform. + * + * @param isCoupled A flag to indicate that the transformer will exit when the resource consumer exits. + */ +public fun SimResourceForwarder(isCoupled: Boolean = false): SimResourceTransformer { + return SimResourceTransformer(isCoupled) { _, command -> command } +} diff --git a/simulator/opendc-simulator/opendc-simulator-resources/src/test/kotlin/org/opendc/simulator/resources/SimResourceForwarderTest.kt b/simulator/opendc-simulator/opendc-simulator-resources/src/test/kotlin/org/opendc/simulator/resources/SimResourceForwarderTest.kt deleted file mode 100644 index 143dbca9..00000000 --- a/simulator/opendc-simulator/opendc-simulator-resources/src/test/kotlin/org/opendc/simulator/resources/SimResourceForwarderTest.kt +++ /dev/null @@ -1,186 +0,0 @@ -/* - * 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.simulator.resources - -import io.mockk.every -import io.mockk.mockk -import io.mockk.spyk -import io.mockk.verify -import kotlinx.coroutines.* -import kotlinx.coroutines.test.runBlockingTest -import org.junit.jupiter.api.Assertions.* -import org.junit.jupiter.api.Test -import org.junit.jupiter.api.assertThrows -import org.opendc.simulator.resources.consumer.SimWorkConsumer -import org.opendc.simulator.utils.DelayControllerClockAdapter -import org.opendc.utils.TimerScheduler - -/** - * A test suite for the [SimResourceForwarder] class. - */ -@OptIn(ExperimentalCoroutinesApi::class) -internal class SimResourceForwarderTest { - @Test - fun testExitImmediately() = runBlockingTest { - val forwarder = SimResourceForwarder() - val clock = DelayControllerClockAdapter(this) - val scheduler = TimerScheduler(coroutineContext, clock) - val source = SimResourceSource(2000.0, clock, scheduler) - - launch { - source.consume(forwarder) - source.close() - } - - forwarder.consume(object : SimResourceConsumer { - override fun onNext(ctx: SimResourceContext): SimResourceCommand { - return SimResourceCommand.Exit - } - }) - - forwarder.close() - scheduler.close() - } - - @Test - fun testExit() = runBlockingTest { - val forwarder = SimResourceForwarder() - val clock = DelayControllerClockAdapter(this) - val scheduler = TimerScheduler(coroutineContext, clock) - val source = SimResourceSource(2000.0, clock, scheduler) - - launch { - source.consume(forwarder) - source.close() - } - - forwarder.consume(object : SimResourceConsumer { - var isFirst = true - - override fun onNext(ctx: SimResourceContext): SimResourceCommand { - return if (isFirst) { - isFirst = false - SimResourceCommand.Consume(10.0, 1.0) - } else { - SimResourceCommand.Exit - } - } - }) - - forwarder.close() - } - - @Test - fun testState() = runBlockingTest { - val forwarder = SimResourceForwarder() - val consumer = object : SimResourceConsumer { - override fun onNext(ctx: SimResourceContext): SimResourceCommand = SimResourceCommand.Exit - } - - assertEquals(SimResourceState.Pending, forwarder.state) - - forwarder.startConsumer(consumer) - assertEquals(SimResourceState.Active, forwarder.state) - - assertThrows { forwarder.startConsumer(consumer) } - - forwarder.cancel() - assertEquals(SimResourceState.Pending, forwarder.state) - - forwarder.close() - assertEquals(SimResourceState.Stopped, forwarder.state) - } - - @Test - fun testCancelPendingDelegate() = runBlockingTest { - val forwarder = SimResourceForwarder() - - val consumer = mockk(relaxUnitFun = true) - every { consumer.onNext(any()) } returns SimResourceCommand.Exit - - forwarder.startConsumer(consumer) - forwarder.cancel() - - verify(exactly = 0) { consumer.onFinish(any(), null) } - } - - @Test - fun testCancelStartedDelegate() = runBlockingTest { - val forwarder = SimResourceForwarder() - val clock = DelayControllerClockAdapter(this) - val scheduler = TimerScheduler(coroutineContext, clock) - val source = SimResourceSource(2000.0, clock, scheduler) - - val consumer = mockk(relaxUnitFun = true) - every { consumer.onNext(any()) } returns SimResourceCommand.Idle(10) - - source.startConsumer(forwarder) - yield() - forwarder.startConsumer(consumer) - yield() - forwarder.cancel() - - verify(exactly = 1) { consumer.onStart(any()) } - verify(exactly = 1) { consumer.onFinish(any(), null) } - } - - @Test - fun testCancelPropagation() = runBlockingTest { - val forwarder = SimResourceForwarder() - val clock = DelayControllerClockAdapter(this) - val scheduler = TimerScheduler(coroutineContext, clock) - val source = SimResourceSource(2000.0, clock, scheduler) - - val consumer = mockk(relaxUnitFun = true) - every { consumer.onNext(any()) } returns SimResourceCommand.Idle(10) - - source.startConsumer(forwarder) - yield() - forwarder.startConsumer(consumer) - yield() - source.cancel() - - verify(exactly = 1) { consumer.onStart(any()) } - verify(exactly = 1) { consumer.onFinish(any(), null) } - } - - @Test - fun testAdjustCapacity() = runBlockingTest { - val forwarder = SimResourceForwarder() - val clock = DelayControllerClockAdapter(this) - val scheduler = TimerScheduler(coroutineContext, clock) - val source = SimResourceSource(1.0, clock, scheduler) - - val consumer = spyk(SimWorkConsumer(2.0, 1.0)) - source.startConsumer(forwarder) - - coroutineScope { - launch { forwarder.consume(consumer) } - delay(1000) - source.capacity = 0.5 - } - - assertEquals(3000, currentTime) - verify(exactly = 1) { consumer.onCapacityChanged(any(), true) } - } -} diff --git a/simulator/opendc-simulator/opendc-simulator-resources/src/test/kotlin/org/opendc/simulator/resources/SimResourceTransformerTest.kt b/simulator/opendc-simulator/opendc-simulator-resources/src/test/kotlin/org/opendc/simulator/resources/SimResourceTransformerTest.kt new file mode 100644 index 00000000..38598f6b --- /dev/null +++ b/simulator/opendc-simulator/opendc-simulator-resources/src/test/kotlin/org/opendc/simulator/resources/SimResourceTransformerTest.kt @@ -0,0 +1,218 @@ +/* + * 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.simulator.resources + +import io.mockk.every +import io.mockk.mockk +import io.mockk.spyk +import io.mockk.verify +import kotlinx.coroutines.* +import kotlinx.coroutines.test.runBlockingTest +import org.junit.jupiter.api.Assertions.* +import org.junit.jupiter.api.Test +import org.junit.jupiter.api.assertThrows +import org.opendc.simulator.resources.consumer.SimWorkConsumer +import org.opendc.simulator.utils.DelayControllerClockAdapter +import org.opendc.utils.TimerScheduler + +/** + * A test suite for the [SimResourceTransformer] class. + */ +@OptIn(ExperimentalCoroutinesApi::class) +internal class SimResourceTransformerTest { + @Test + fun testExitImmediately() = runBlockingTest { + val forwarder = SimResourceForwarder() + val clock = DelayControllerClockAdapter(this) + val scheduler = TimerScheduler(coroutineContext, clock) + val source = SimResourceSource(2000.0, clock, scheduler) + + launch { + source.consume(forwarder) + source.close() + } + + forwarder.consume(object : SimResourceConsumer { + override fun onNext(ctx: SimResourceContext): SimResourceCommand { + return SimResourceCommand.Exit + } + }) + + forwarder.close() + scheduler.close() + } + + @Test + fun testExit() = runBlockingTest { + val forwarder = SimResourceForwarder() + val clock = DelayControllerClockAdapter(this) + val scheduler = TimerScheduler(coroutineContext, clock) + val source = SimResourceSource(2000.0, clock, scheduler) + + launch { + source.consume(forwarder) + source.close() + } + + forwarder.consume(object : SimResourceConsumer { + var isFirst = true + + override fun onNext(ctx: SimResourceContext): SimResourceCommand { + return if (isFirst) { + isFirst = false + SimResourceCommand.Consume(10.0, 1.0) + } else { + SimResourceCommand.Exit + } + } + }) + + forwarder.close() + } + + @Test + fun testState() = runBlockingTest { + val forwarder = SimResourceForwarder() + val consumer = object : SimResourceConsumer { + override fun onNext(ctx: SimResourceContext): SimResourceCommand = SimResourceCommand.Exit + } + + assertEquals(SimResourceState.Pending, forwarder.state) + + forwarder.startConsumer(consumer) + assertEquals(SimResourceState.Active, forwarder.state) + + assertThrows { forwarder.startConsumer(consumer) } + + forwarder.cancel() + assertEquals(SimResourceState.Pending, forwarder.state) + + forwarder.close() + assertEquals(SimResourceState.Stopped, forwarder.state) + } + + @Test + fun testCancelPendingDelegate() = runBlockingTest { + val forwarder = SimResourceForwarder() + + val consumer = mockk(relaxUnitFun = true) + every { consumer.onNext(any()) } returns SimResourceCommand.Exit + + forwarder.startConsumer(consumer) + forwarder.cancel() + + verify(exactly = 0) { consumer.onFinish(any(), null) } + } + + @Test + fun testCancelStartedDelegate() = runBlockingTest { + val forwarder = SimResourceForwarder() + val clock = DelayControllerClockAdapter(this) + val scheduler = TimerScheduler(coroutineContext, clock) + val source = SimResourceSource(2000.0, clock, scheduler) + + val consumer = mockk(relaxUnitFun = true) + every { consumer.onNext(any()) } returns SimResourceCommand.Idle(10) + + source.startConsumer(forwarder) + yield() + forwarder.startConsumer(consumer) + yield() + forwarder.cancel() + + verify(exactly = 1) { consumer.onStart(any()) } + verify(exactly = 1) { consumer.onFinish(any(), null) } + } + + @Test + fun testCancelPropagation() = runBlockingTest { + val forwarder = SimResourceForwarder() + val clock = DelayControllerClockAdapter(this) + val scheduler = TimerScheduler(coroutineContext, clock) + val source = SimResourceSource(2000.0, clock, scheduler) + + val consumer = mockk(relaxUnitFun = true) + every { consumer.onNext(any()) } returns SimResourceCommand.Idle(10) + + source.startConsumer(forwarder) + yield() + forwarder.startConsumer(consumer) + yield() + source.cancel() + + verify(exactly = 1) { consumer.onStart(any()) } + verify(exactly = 1) { consumer.onFinish(any(), null) } + } + + @Test + fun testExitPropagation() = runBlockingTest { + val forwarder = SimResourceForwarder(isCoupled = true) + val clock = DelayControllerClockAdapter(this) + val scheduler = TimerScheduler(coroutineContext, clock) + val source = SimResourceSource(2000.0, clock, scheduler) + + val consumer = mockk(relaxUnitFun = true) + every { consumer.onNext(any()) } returns SimResourceCommand.Exit + + source.startConsumer(forwarder) + forwarder.consume(consumer) + yield() + + assertEquals(SimResourceState.Pending, source.state) + } + + @Test + fun testAdjustCapacity() = runBlockingTest { + val forwarder = SimResourceForwarder() + val clock = DelayControllerClockAdapter(this) + val scheduler = TimerScheduler(coroutineContext, clock) + val source = SimResourceSource(1.0, clock, scheduler) + + val consumer = spyk(SimWorkConsumer(2.0, 1.0)) + source.startConsumer(forwarder) + + coroutineScope { + launch { forwarder.consume(consumer) } + delay(1000) + source.capacity = 0.5 + } + + assertEquals(3000, currentTime) + verify(exactly = 1) { consumer.onCapacityChanged(any(), true) } + } + + @Test + fun testTransformExit() = runBlockingTest { + val forwarder = SimResourceTransformer { _, _ -> SimResourceCommand.Exit } + val clock = DelayControllerClockAdapter(this) + val scheduler = TimerScheduler(coroutineContext, clock) + val source = SimResourceSource(1.0, clock, scheduler) + + val consumer = spyk(SimWorkConsumer(2.0, 1.0)) + source.startConsumer(forwarder) + forwarder.consume(consumer) + + assertEquals(0, currentTime) + verify(exactly = 1) { consumer.onNext(any()) } + } +} -- cgit v1.2.3 From 69598598be2c248acc49e40607b3dd0998ec1ca5 Mon Sep 17 00:00:00 2001 From: Fabian Mastenbroek Date: Wed, 24 Mar 2021 13:05:33 +0100 Subject: simulator: Move power models to simulator module This change moves the power models from the `opendc-compute-simulator` to the `opendc-simulator-compute` module, since it better fits the scope of the models and allows them to be re-used for other purposes. --- .../kotlin/org/opendc/compute/simulator/SimHost.kt | 19 ++--- .../compute/simulator/power/api/CpuPowerModel.kt | 28 ------- .../compute/simulator/power/api/Powerable.kt | 37 --------- .../simulator/power/models/ConstantPowerModel.kt | 10 --- .../simulator/power/models/CubicPowerModel.kt | 26 ------ .../power/models/InterpolationPowerModel.kt | 34 -------- .../simulator/power/models/LinearPowerModel.kt | 25 ------ .../simulator/power/models/PStatePowerModel.kt | 96 ---------------------- .../simulator/power/models/SqrtPowerModel.kt | 26 ------ .../simulator/power/models/SquarePowerModel.kt | 26 ------ .../power/models/ZeroIdlePowerDecorator.kt | 14 ---- .../simulator/power/models/package-info.java | 12 --- .../compute/simulator/power/CpuPowerModelTest.kt | 77 ----------------- .../simulator/power/PStatePowerModelTest.kt | 45 ---------- .../experiments/capelin/ExperimentHelpers.kt | 2 +- .../org/opendc/format/environment/MachineDef.kt | 4 +- .../environment/sc18/Sc18EnvironmentReader.kt | 2 +- .../sc20/Sc20ClusterEnvironmentReader.kt | 2 +- .../environment/sc20/Sc20EnvironmentReader.kt | 2 +- .../kotlin/org/opendc/runner/web/TopologyParser.kt | 2 +- .../simulator/compute/SimBareMetalMachine.kt | 17 +++- .../simulator/compute/power/ConstantPowerModel.kt | 8 ++ .../simulator/compute/power/CubicPowerModel.kt | 25 ++++++ .../compute/power/InterpolationPowerModel.kt | 33 ++++++++ .../simulator/compute/power/LinearPowerModel.kt | 23 ++++++ .../simulator/compute/power/MachinePowerModel.kt | 18 ++++ .../simulator/compute/power/PStatePowerModel.kt | 96 ++++++++++++++++++++++ .../simulator/compute/power/SqrtPowerModel.kt | 25 ++++++ .../simulator/compute/power/SquarePowerModel.kt | 25 ++++++ .../compute/power/ZeroIdlePowerDecorator.kt | 12 +++ .../compute/power/MachinePowerModelTest.kt | 50 +++++++++++ .../compute/power/PStatePowerModelTest.kt | 44 ++++++++++ 32 files changed, 387 insertions(+), 478 deletions(-) delete mode 100644 simulator/opendc-compute/opendc-compute-simulator/src/main/kotlin/org/opendc/compute/simulator/power/api/CpuPowerModel.kt delete mode 100644 simulator/opendc-compute/opendc-compute-simulator/src/main/kotlin/org/opendc/compute/simulator/power/api/Powerable.kt delete mode 100644 simulator/opendc-compute/opendc-compute-simulator/src/main/kotlin/org/opendc/compute/simulator/power/models/ConstantPowerModel.kt delete mode 100644 simulator/opendc-compute/opendc-compute-simulator/src/main/kotlin/org/opendc/compute/simulator/power/models/CubicPowerModel.kt delete mode 100644 simulator/opendc-compute/opendc-compute-simulator/src/main/kotlin/org/opendc/compute/simulator/power/models/InterpolationPowerModel.kt delete mode 100644 simulator/opendc-compute/opendc-compute-simulator/src/main/kotlin/org/opendc/compute/simulator/power/models/LinearPowerModel.kt delete mode 100644 simulator/opendc-compute/opendc-compute-simulator/src/main/kotlin/org/opendc/compute/simulator/power/models/PStatePowerModel.kt delete mode 100644 simulator/opendc-compute/opendc-compute-simulator/src/main/kotlin/org/opendc/compute/simulator/power/models/SqrtPowerModel.kt delete mode 100644 simulator/opendc-compute/opendc-compute-simulator/src/main/kotlin/org/opendc/compute/simulator/power/models/SquarePowerModel.kt delete mode 100644 simulator/opendc-compute/opendc-compute-simulator/src/main/kotlin/org/opendc/compute/simulator/power/models/ZeroIdlePowerDecorator.kt delete mode 100644 simulator/opendc-compute/opendc-compute-simulator/src/main/kotlin/org/opendc/compute/simulator/power/models/package-info.java delete mode 100644 simulator/opendc-compute/opendc-compute-simulator/src/test/kotlin/org/opendc/compute/simulator/power/CpuPowerModelTest.kt delete mode 100644 simulator/opendc-compute/opendc-compute-simulator/src/test/kotlin/org/opendc/compute/simulator/power/PStatePowerModelTest.kt create mode 100644 simulator/opendc-simulator/opendc-simulator-compute/src/main/kotlin/org/opendc/simulator/compute/power/ConstantPowerModel.kt create mode 100644 simulator/opendc-simulator/opendc-simulator-compute/src/main/kotlin/org/opendc/simulator/compute/power/CubicPowerModel.kt create mode 100644 simulator/opendc-simulator/opendc-simulator-compute/src/main/kotlin/org/opendc/simulator/compute/power/InterpolationPowerModel.kt create mode 100644 simulator/opendc-simulator/opendc-simulator-compute/src/main/kotlin/org/opendc/simulator/compute/power/LinearPowerModel.kt create mode 100644 simulator/opendc-simulator/opendc-simulator-compute/src/main/kotlin/org/opendc/simulator/compute/power/MachinePowerModel.kt create mode 100644 simulator/opendc-simulator/opendc-simulator-compute/src/main/kotlin/org/opendc/simulator/compute/power/PStatePowerModel.kt create mode 100644 simulator/opendc-simulator/opendc-simulator-compute/src/main/kotlin/org/opendc/simulator/compute/power/SqrtPowerModel.kt create mode 100644 simulator/opendc-simulator/opendc-simulator-compute/src/main/kotlin/org/opendc/simulator/compute/power/SquarePowerModel.kt create mode 100644 simulator/opendc-simulator/opendc-simulator-compute/src/main/kotlin/org/opendc/simulator/compute/power/ZeroIdlePowerDecorator.kt create mode 100644 simulator/opendc-simulator/opendc-simulator-compute/src/test/kotlin/org/opendc/simulator/compute/power/MachinePowerModelTest.kt create mode 100644 simulator/opendc-simulator/opendc-simulator-compute/src/test/kotlin/org/opendc/simulator/compute/power/PStatePowerModelTest.kt diff --git a/simulator/opendc-compute/opendc-compute-simulator/src/main/kotlin/org/opendc/compute/simulator/SimHost.kt b/simulator/opendc-compute/opendc-compute-simulator/src/main/kotlin/org/opendc/compute/simulator/SimHost.kt index 694676bc..3c4b4410 100644 --- a/simulator/opendc-compute/opendc-compute-simulator/src/main/kotlin/org/opendc/compute/simulator/SimHost.kt +++ b/simulator/opendc-compute/opendc-compute-simulator/src/main/kotlin/org/opendc/compute/simulator/SimHost.kt @@ -29,13 +29,12 @@ import org.opendc.compute.api.Flavor import org.opendc.compute.api.Server import org.opendc.compute.api.ServerState import org.opendc.compute.service.driver.* -import org.opendc.compute.simulator.power.api.CpuPowerModel -import org.opendc.compute.simulator.power.api.Powerable -import org.opendc.compute.simulator.power.models.ConstantPowerModel import org.opendc.simulator.compute.* import org.opendc.simulator.compute.interference.IMAGE_PERF_INTERFERENCE_MODEL import org.opendc.simulator.compute.interference.PerformanceInterferenceModel import org.opendc.simulator.compute.model.MemoryUnit +import org.opendc.simulator.compute.power.ConstantPowerModel +import org.opendc.simulator.compute.power.MachinePowerModel import org.opendc.simulator.failures.FailureDomain import org.opendc.utils.flow.EventFlow import java.time.Clock @@ -54,9 +53,9 @@ public class SimHost( context: CoroutineContext, clock: Clock, hypervisor: SimHypervisorProvider, - cpuPowerModel: CpuPowerModel = ConstantPowerModel(0.0), - private val mapper: SimWorkloadMapper = SimMetaWorkloadMapper() -) : Host, FailureDomain, Powerable, AutoCloseable { + powerModel: MachinePowerModel = ConstantPowerModel(0.0), + private val mapper: SimWorkloadMapper = SimMetaWorkloadMapper(), +) : Host, FailureDomain, AutoCloseable { /** * The [CoroutineScope] of the host bounded by the lifecycle of the host. */ @@ -84,7 +83,7 @@ public class SimHost( /** * The machine to run on. */ - public val machine: SimBareMetalMachine = SimBareMetalMachine(context, clock, model) + public val machine: SimBareMetalMachine = SimBareMetalMachine(context, clock, model, powerModel) /** * The hypervisor to run multiple workloads. @@ -133,8 +132,6 @@ public class SimHost( override val model: HostModel = HostModel(model.cpus.size, model.memory.map { it.size }.sum()) - override val powerDraw: Flow = cpuPowerModel.getPowerDraw(machine) - init { // Launch hypervisor onto machine scope.launch { @@ -223,7 +220,7 @@ public class SimHost( } private fun onGuestStart(vm: Guest) { - guests.forEach { _, guest -> + guests.forEach { (_, guest) -> if (guest.state == ServerState.RUNNING) { vm.performanceInterferenceModel?.onStart(vm.server.image.name) } @@ -233,7 +230,7 @@ public class SimHost( } private fun onGuestStop(vm: Guest) { - guests.forEach { _, guest -> + guests.forEach { (_, guest) -> if (guest.state == ServerState.RUNNING) { vm.performanceInterferenceModel?.onStop(vm.server.image.name) } diff --git a/simulator/opendc-compute/opendc-compute-simulator/src/main/kotlin/org/opendc/compute/simulator/power/api/CpuPowerModel.kt b/simulator/opendc-compute/opendc-compute-simulator/src/main/kotlin/org/opendc/compute/simulator/power/api/CpuPowerModel.kt deleted file mode 100644 index 893f7ab1..00000000 --- a/simulator/opendc-compute/opendc-compute-simulator/src/main/kotlin/org/opendc/compute/simulator/power/api/CpuPowerModel.kt +++ /dev/null @@ -1,28 +0,0 @@ -package org.opendc.compute.simulator.power.api - -import kotlinx.coroutines.flow.Flow -import kotlinx.coroutines.flow.map -import org.opendc.simulator.compute.SimMachine - -public interface CpuPowerModel { - /** - * Computes CPU power consumption for each host. - * - * @param cpuUtil The CPU utilization percentage. - * @return A [Double] value of CPU power consumption. - * @throws IllegalArgumentException Will throw an error if [cpuUtil] is out of range. - */ - @Throws(IllegalArgumentException::class) - public fun computeCpuPower(cpuUtil: Double): Double - - /** - * Emits the values of power consumption for servers. - * - * @param machine The [SimMachine] that the model is measuring. - * @return A [Flow] of values representing the server power draw. - */ - public fun getPowerDraw(machine: SimMachine): Flow = - machine.usage.map { - computeCpuPower(it) - } -} diff --git a/simulator/opendc-compute/opendc-compute-simulator/src/main/kotlin/org/opendc/compute/simulator/power/api/Powerable.kt b/simulator/opendc-compute/opendc-compute-simulator/src/main/kotlin/org/opendc/compute/simulator/power/api/Powerable.kt deleted file mode 100644 index 780f2a29..00000000 --- a/simulator/opendc-compute/opendc-compute-simulator/src/main/kotlin/org/opendc/compute/simulator/power/api/Powerable.kt +++ /dev/null @@ -1,37 +0,0 @@ -/* - * 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. - */ - -package org.opendc.compute.simulator.power.api - -import kotlinx.coroutines.flow.Flow - -/** - * An entity that is uses power from some power source. - */ -public interface Powerable { - /** - * The power draw at the device's power supply in watts (W).w - */ - public val powerDraw: Flow -} diff --git a/simulator/opendc-compute/opendc-compute-simulator/src/main/kotlin/org/opendc/compute/simulator/power/models/ConstantPowerModel.kt b/simulator/opendc-compute/opendc-compute-simulator/src/main/kotlin/org/opendc/compute/simulator/power/models/ConstantPowerModel.kt deleted file mode 100644 index 5e80827b..00000000 --- a/simulator/opendc-compute/opendc-compute-simulator/src/main/kotlin/org/opendc/compute/simulator/power/models/ConstantPowerModel.kt +++ /dev/null @@ -1,10 +0,0 @@ -package org.opendc.compute.simulator.power.models - -import org.opendc.compute.simulator.power.api.CpuPowerModel - -/** - * A power model which produces a constant value [constant]. - */ -public class ConstantPowerModel(private val constant: Double) : CpuPowerModel { - public override fun computeCpuPower(cpuUtil: Double): Double = constant -} diff --git a/simulator/opendc-compute/opendc-compute-simulator/src/main/kotlin/org/opendc/compute/simulator/power/models/CubicPowerModel.kt b/simulator/opendc-compute/opendc-compute-simulator/src/main/kotlin/org/opendc/compute/simulator/power/models/CubicPowerModel.kt deleted file mode 100644 index 9008a987..00000000 --- a/simulator/opendc-compute/opendc-compute-simulator/src/main/kotlin/org/opendc/compute/simulator/power/models/CubicPowerModel.kt +++ /dev/null @@ -1,26 +0,0 @@ -package org.opendc.compute.simulator.power.models - -import org.opendc.compute.simulator.power.api.CpuPowerModel -import kotlin.math.pow - -/** - * The cubic power model partially adapted from CloudSim. - * - * @param maxPower The maximum power draw in Watts of the server. - * @param staticPowerPercent The static power percentage. - * @property staticPower The static power consumption that is not dependent on resource usage. - * It is the amount of energy consumed even when the host is idle. - * @property constPower The constant power consumption for each fraction of resource used. - */ -public class CubicPowerModel( - private var maxPower: Double, - staticPowerPercent: Double -) : CpuPowerModel { - private var staticPower: Double = staticPowerPercent * maxPower - private var constPower: Double = (maxPower - staticPower) / 100.0.pow(3) - - public override fun computeCpuPower(cpuUtil: Double): Double { - require(cpuUtil in 0.0..1.0) { "CPU utilization must be in [0, 1]" } - return staticPower + constPower * (cpuUtil * 100).pow(3) - } -} diff --git a/simulator/opendc-compute/opendc-compute-simulator/src/main/kotlin/org/opendc/compute/simulator/power/models/InterpolationPowerModel.kt b/simulator/opendc-compute/opendc-compute-simulator/src/main/kotlin/org/opendc/compute/simulator/power/models/InterpolationPowerModel.kt deleted file mode 100644 index b01957e4..00000000 --- a/simulator/opendc-compute/opendc-compute-simulator/src/main/kotlin/org/opendc/compute/simulator/power/models/InterpolationPowerModel.kt +++ /dev/null @@ -1,34 +0,0 @@ -package org.opendc.compute.simulator.power.models - -import org.opendc.compute.simulator.power.api.CpuPowerModel -import kotlin.math.ceil -import kotlin.math.floor - -/** - * The linear interpolation power model partially adapted from CloudSim. - * - * @param maxPower The maximum power draw in Watts of the server. - * @param staticPowerPercent The static power percentage. - * @property staticPower The static power consumption that is not dependent on resource usage. - * It is the amount of energy consumed even when the host is idle. - * @property constPower The constant power consumption for each fraction of resource used. - */ -public abstract class InterpolationPowerModel : CpuPowerModel { - - public override fun computeCpuPower(cpuUtil: Double): Double { - require(cpuUtil in 0.0..1.0) { "CPU utilization must be in [0, 1]" } - - val cpuUtilFlr = floor(cpuUtil * 10).toInt() - val cpuUtilCil = ceil(cpuUtil * 10).toInt() - val power1: Double = getPowerData(cpuUtilFlr) - val power2: Double = getPowerData(cpuUtilCil) - val delta = (power2 - power1) / 10 - - return if (cpuUtil % 0.1 == 0.0) - getPowerData((cpuUtil * 10).toInt()) - else - power1 + delta * (cpuUtil - cpuUtilFlr.toDouble() / 10) * 100 - } - - public abstract fun getPowerData(index: Int): Double -} diff --git a/simulator/opendc-compute/opendc-compute-simulator/src/main/kotlin/org/opendc/compute/simulator/power/models/LinearPowerModel.kt b/simulator/opendc-compute/opendc-compute-simulator/src/main/kotlin/org/opendc/compute/simulator/power/models/LinearPowerModel.kt deleted file mode 100644 index 913095ad..00000000 --- a/simulator/opendc-compute/opendc-compute-simulator/src/main/kotlin/org/opendc/compute/simulator/power/models/LinearPowerModel.kt +++ /dev/null @@ -1,25 +0,0 @@ -package org.opendc.compute.simulator.power.models - -import org.opendc.compute.simulator.power.api.CpuPowerModel - -/** - * The linear power model partially adapted from CloudSim. - * - * @param maxPower The maximum power draw in Watts of the server. - * @param staticPowerPercent The static power percentage. - * @property staticPower The static power consumption that is not dependent on resource usage. - * It is the amount of energy consumed even when the host is idle. - * @property constPower The constant power consumption for each fraction of resource used. - */ -public class LinearPowerModel( - private var maxPower: Double, - staticPowerPercent: Double -) : CpuPowerModel { - private var staticPower: Double = staticPowerPercent * maxPower - private var constPower: Double = (maxPower - staticPower) / 100 - - public override fun computeCpuPower(cpuUtil: Double): Double { - require(cpuUtil in 0.0..1.0) { "CPU utilization must be in [0, 1]" } - return staticPower + constPower * cpuUtil * 100 - } -} diff --git a/simulator/opendc-compute/opendc-compute-simulator/src/main/kotlin/org/opendc/compute/simulator/power/models/PStatePowerModel.kt b/simulator/opendc-compute/opendc-compute-simulator/src/main/kotlin/org/opendc/compute/simulator/power/models/PStatePowerModel.kt deleted file mode 100644 index aea089da..00000000 --- a/simulator/opendc-compute/opendc-compute-simulator/src/main/kotlin/org/opendc/compute/simulator/power/models/PStatePowerModel.kt +++ /dev/null @@ -1,96 +0,0 @@ -package org.opendc.compute.simulator.power.models - -import org.opendc.simulator.compute.SimBareMetalMachine -import java.time.Clock -import java.util.* - -/** - * The CPU power model derived from the iCanCloud simulator. - * - * @param machine The [SimBareMetalMachine] that the model is measuring. - * @param clock The virtual [Clock] to track the time spent at each p-state. - * @property cpuPowerMeter A [MutableMap] that contains CPU frequencies ([Double]) in GHz - * as keys and the time ([Long]) spent in that frequency range in seconds. - * @property pStatesToPower A [TreeMap] that contains the frequency ([Double]) of corresponding p-state in GHz - * as keys and the energy ([Double]) consumption of that state in Watts. - * @property pStatesRange A [Pair] in which the fist and second elements are the lower and upper bounds of the - * consumption values of the p-states respectively. - * @property lastMeasureTime The last update time of the [cpuPowerMeter]. - * @property currPState The p-state that the model is currently in. - */ -public class PStatePowerModel( - private val machine: SimBareMetalMachine, - private val clock: Clock, -) { - // TODO: Extract the power meter out of the model. - private val cpuPowerMeter = mutableMapOf() - private val pStatesToPower = TreeMap() - private val pStatesRange: Pair - private var lastMeasureTime: Long - private var currPState: Double - - init { - loadPStates(this) - pStatesRange = Pair(pStatesToPower.keys.first(), pStatesToPower.keys.last()) - pStatesToPower.keys.forEach { cpuPowerMeter[it] = 0L } - currPState = pStatesRange.first - lastMeasureTime = getClockInstant() - updateCpuPowerMeter() - } - - /** Recorde the elapsed time to the corresponding p-state. */ - public fun updateCpuPowerMeter() { - val newMeasureTime = getClockInstant() - val newMaxFreq: Double = getMaxCpuSpeedInGHz() - assert(newMaxFreq in pStatesRange.first..pStatesRange.second) { - "The maximum frequency $newMaxFreq is not in the range of the P-state frequency " + - "from ${pStatesRange.first} to ${pStatesRange.second}." - } - - // Update the current p-state level on which the CPU is running. - val newPState = pStatesToPower.ceilingKey(newMaxFreq) - - // Add the time elapsed to the previous state. - cpuPowerMeter.merge(currPState, newMeasureTime - lastMeasureTime, Long::plus) - - // Update the current states. - currPState = newPState - lastMeasureTime = newMeasureTime - } - - /** Get the power value of the energy consumption level at which the CPU is working. */ - public fun getInstantCpuPower(): Double = - pStatesToPower.getOrDefault(currPState, 0.0) - - /** Get the accumulated power consumption up until now. */ - public fun getAccumulatedCpuPower(): Double = - pStatesToPower.keys - .map { - pStatesToPower.getOrDefault(it, 0.0) * - cpuPowerMeter.getOrDefault(it, 0.0).toDouble() - }.sum() - - private fun getClockInstant() = clock.millis() / 1000 - - /** Get the maximum frequency of the CPUs in GHz as that of the package. - * @see - * on page 34. - */ - private fun getMaxCpuSpeedInGHz() = (machine.speed.maxOrNull() ?: 0.0) / 1000 - - public companion object PStatesLoader { - private fun loadPStates(pStatePowerModel: PStatePowerModel) { - // TODO: Dynamically load configuration. - // See P4 of https://www.intel.com/content/dam/support/us/en/documents/motherboards/server/sb/power_management_of_intel_architecture_servers.pdf - pStatePowerModel.pStatesToPower.putAll( - sortedMapOf( - 3.6 to 103.0, - 3.4 to 94.0, - 3.2 to 85.0, - 3.0 to 76.0, - 2.8 to 8.0, - ) - ) - } - } -} diff --git a/simulator/opendc-compute/opendc-compute-simulator/src/main/kotlin/org/opendc/compute/simulator/power/models/SqrtPowerModel.kt b/simulator/opendc-compute/opendc-compute-simulator/src/main/kotlin/org/opendc/compute/simulator/power/models/SqrtPowerModel.kt deleted file mode 100644 index 85d94ffc..00000000 --- a/simulator/opendc-compute/opendc-compute-simulator/src/main/kotlin/org/opendc/compute/simulator/power/models/SqrtPowerModel.kt +++ /dev/null @@ -1,26 +0,0 @@ -package org.opendc.compute.simulator.power.models - -import org.opendc.compute.simulator.power.api.CpuPowerModel -import kotlin.math.sqrt - -/** - * The square root power model partially adapted from CloudSim. - * - * @param maxPower The maximum power draw in Watts of the server. - * @param staticPowerPercent The static power percentage. - * @property staticPower The static power consumption that is not dependent on resource usage. - * It is the amount of energy consumed even when the host is idle. - * @property constPower The constant power consumption for each fraction of resource used. - */ -public class SqrtPowerModel( - private var maxPower: Double, - staticPowerPercent: Double -) : CpuPowerModel { - private var staticPower: Double = staticPowerPercent * maxPower - private var constPower: Double = (maxPower - staticPower) / sqrt(100.0) - - override fun computeCpuPower(cpuUtil: Double): Double { - require(cpuUtil in 0.0..1.0) { "CPU utilization must be in [0, 1]" } - return staticPower + constPower * sqrt(cpuUtil * 100) - } -} diff --git a/simulator/opendc-compute/opendc-compute-simulator/src/main/kotlin/org/opendc/compute/simulator/power/models/SquarePowerModel.kt b/simulator/opendc-compute/opendc-compute-simulator/src/main/kotlin/org/opendc/compute/simulator/power/models/SquarePowerModel.kt deleted file mode 100644 index 5f44aa3c..00000000 --- a/simulator/opendc-compute/opendc-compute-simulator/src/main/kotlin/org/opendc/compute/simulator/power/models/SquarePowerModel.kt +++ /dev/null @@ -1,26 +0,0 @@ -package org.opendc.compute.simulator.power.models - -import org.opendc.compute.simulator.power.api.CpuPowerModel -import kotlin.math.pow - -/** - * The square power model partially adapted from CloudSim. - * - * @param maxPower The maximum power draw in Watts of the server. - * @param staticPowerPercent The static power percentage. - * @property staticPower The static power consumption that is not dependent on resource usage. - * It is the amount of energy consumed even when the host is idle. - * @property constPower The constant power consumption for each fraction of resource used. - */ -public class SquarePowerModel( - private var maxPower: Double, - staticPowerPercent: Double -) : CpuPowerModel { - private var staticPower: Double = staticPowerPercent * maxPower - private var constPower: Double = (maxPower - staticPower) / 100.0.pow(2) - - override fun computeCpuPower(cpuUtil: Double): Double { - require(cpuUtil in 0.0..1.0) { "CPU utilization must be in [0, 1]" } - return staticPower + constPower * (cpuUtil * 100).pow(2) - } -} diff --git a/simulator/opendc-compute/opendc-compute-simulator/src/main/kotlin/org/opendc/compute/simulator/power/models/ZeroIdlePowerDecorator.kt b/simulator/opendc-compute/opendc-compute-simulator/src/main/kotlin/org/opendc/compute/simulator/power/models/ZeroIdlePowerDecorator.kt deleted file mode 100644 index b0c3fa4c..00000000 --- a/simulator/opendc-compute/opendc-compute-simulator/src/main/kotlin/org/opendc/compute/simulator/power/models/ZeroIdlePowerDecorator.kt +++ /dev/null @@ -1,14 +0,0 @@ -package org.opendc.compute.simulator.power.models - -import org.opendc.compute.simulator.power.api.CpuPowerModel - -/** - * A decorator for ignoring the idle power when computing energy consumption of components. - * - * @param delegate The [CpuPowerModel] to delegate to. - */ -public class ZeroIdlePowerDecorator(private val delegate: CpuPowerModel) : CpuPowerModel { - override fun computeCpuPower(cpuUtil: Double): Double { - return if (cpuUtil == 0.0) 0.0 else delegate.computeCpuPower(cpuUtil) - } -} diff --git a/simulator/opendc-compute/opendc-compute-simulator/src/main/kotlin/org/opendc/compute/simulator/power/models/package-info.java b/simulator/opendc-compute/opendc-compute-simulator/src/main/kotlin/org/opendc/compute/simulator/power/models/package-info.java deleted file mode 100644 index 9354f1f9..00000000 --- a/simulator/opendc-compute/opendc-compute-simulator/src/main/kotlin/org/opendc/compute/simulator/power/models/package-info.java +++ /dev/null @@ -1,12 +0,0 @@ -/** - * Contains a set of energy models, which are developed based on that of the following cloud simulators. - * N.B. Different configurations and new features may have been introduced. - * - * CloudSim: - * @see Anton Beloglazov, and Rajkumar Buyya, "Optimal Online Deterministic - * Algorithms and Adaptive Heuristics for Energy and Performance Efficient Dynamic Consolidation of Virtual Machines in - * Cloud Data Centers", Concurrency and Computation: Practice and Experience (CCPE), Volume 24, - * Issue 13, Pages: 1397-1420, John Wiley & Sons, Ltd, New York, USA, 2012 - */ -package org.opendc.compute.simulator.power.models; -// rest of the file is empty diff --git a/simulator/opendc-compute/opendc-compute-simulator/src/test/kotlin/org/opendc/compute/simulator/power/CpuPowerModelTest.kt b/simulator/opendc-compute/opendc-compute-simulator/src/test/kotlin/org/opendc/compute/simulator/power/CpuPowerModelTest.kt deleted file mode 100644 index cd18b120..00000000 --- a/simulator/opendc-compute/opendc-compute-simulator/src/test/kotlin/org/opendc/compute/simulator/power/CpuPowerModelTest.kt +++ /dev/null @@ -1,77 +0,0 @@ -package org.opendc.compute.simulator.power - -import io.mockk.* -import kotlinx.coroutines.ExperimentalCoroutinesApi -import kotlinx.coroutines.flow.* -import kotlinx.coroutines.test.runBlockingTest -import org.junit.jupiter.api.Assertions.assertEquals -import org.junit.jupiter.params.ParameterizedTest -import org.junit.jupiter.params.provider.Arguments -import org.junit.jupiter.params.provider.MethodSource -import org.opendc.compute.simulator.power.api.CpuPowerModel -import org.opendc.compute.simulator.power.models.* -import org.opendc.simulator.compute.SimBareMetalMachine -import java.util.stream.Stream -import kotlin.math.pow - -@OptIn(ExperimentalCoroutinesApi::class) -internal class CpuPowerModelTest { - private val epsilon = 10.0.pow(-3) - private val cpuUtil = 0.9 - - @ParameterizedTest - @MethodSource("cpuPowerModelArgs") - fun `compute power consumption given CPU loads`( - powerModel: CpuPowerModel, - expectedPowerConsumption: Double - ) { - val computedPowerConsumption = powerModel.computeCpuPower(cpuUtil) - assertEquals(expectedPowerConsumption, computedPowerConsumption, epsilon) - } - - @ParameterizedTest - @MethodSource("cpuPowerModelArgs") - fun `ignore idle power when computing power consumptions`( - powerModel: CpuPowerModel, - expectedPowerConsumption: Double - ) { - val zeroPowerModel = ZeroIdlePowerDecorator(powerModel) - val computedPowerConsumption = zeroPowerModel.computeCpuPower(0.0) - assertEquals(0.0, computedPowerConsumption) - } - - @ParameterizedTest - @MethodSource("cpuPowerModelArgs") - fun `emit power draw for hosts by different models`( - powerModel: CpuPowerModel, - expectedPowerConsumption: Double - ) { - runBlockingTest { - val cpuLoads = flowOf(cpuUtil, cpuUtil, cpuUtil).stateIn(this) - val machine = mockkClass(SimBareMetalMachine::class) - every { machine.usage } returns cpuLoads - - val serverPowerDraw = powerModel.getPowerDraw(machine) - - assertEquals( - serverPowerDraw.first().toDouble(), - flowOf(expectedPowerConsumption).first().toDouble(), - epsilon - ) - - verify(exactly = 1) { machine.usage } - } - } - - @Suppress("unused") - private companion object { - @JvmStatic - fun cpuPowerModelArgs(): Stream = Stream.of( - Arguments.of(ConstantPowerModel(0.0), 0.0), - Arguments.of(LinearPowerModel(350.0, 200 / 350.0), 335.0), - Arguments.of(SquarePowerModel(350.0, 200 / 350.0), 321.5), - Arguments.of(CubicPowerModel(350.0, 200 / 350.0), 309.35), - Arguments.of(SqrtPowerModel(350.0, 200 / 350.0), 342.302), - ) - } -} diff --git a/simulator/opendc-compute/opendc-compute-simulator/src/test/kotlin/org/opendc/compute/simulator/power/PStatePowerModelTest.kt b/simulator/opendc-compute/opendc-compute-simulator/src/test/kotlin/org/opendc/compute/simulator/power/PStatePowerModelTest.kt deleted file mode 100644 index e144e541..00000000 --- a/simulator/opendc-compute/opendc-compute-simulator/src/test/kotlin/org/opendc/compute/simulator/power/PStatePowerModelTest.kt +++ /dev/null @@ -1,45 +0,0 @@ -package org.opendc.compute.simulator.power - -import io.mockk.* -import org.junit.jupiter.api.Assertions.assertEquals -import org.junit.jupiter.api.Test -import org.opendc.compute.simulator.power.models.PStatePowerModel -import org.opendc.simulator.compute.SimBareMetalMachine -import java.time.Clock - -internal class PStatePowerModelTest { - @Test - fun `update CPU power meter with P-states`() { - val p0Power = 8.0 - val p3Power = 94.0 - val p4Power = 103.0 - val expectedP0Power = 8.0 * 10 - val expectedP0P4Power = expectedP0Power + 103.0 * 10 - - val clock = mockkClass(Clock::class) - val machine = mockkClass(SimBareMetalMachine::class) - every { clock.millis() } returnsMany listOf(0L, 0L, 10_000L, 20_000L) - every { machine.speed } returns - listOf(2.8, 2.8, 2.8, 2.8).map { it * 1000 } andThen // Max. 2.8MHz covered by P0 - listOf(1.5, 3.1, 3.3, 3.6).map { it * 1000 } andThen // Max. 3.6MHz covered by P4 - listOf(1.5, 3.1, 3.1, 3.3).map { it * 1000 } // Max. 3.3MHz covered by P3 - - // Power meter initialization. - val pStatePowerModel = PStatePowerModel(machine, clock) - verify(exactly = 2) { clock.millis() } - verify(exactly = 1) { machine.speed } - assertEquals(p0Power, pStatePowerModel.getInstantCpuPower()) - - // The first measure. - pStatePowerModel.updateCpuPowerMeter() - assertEquals(p4Power, pStatePowerModel.getInstantCpuPower()) - assertEquals(expectedP0Power, pStatePowerModel.getAccumulatedCpuPower()) - - // The second measure. - pStatePowerModel.updateCpuPowerMeter() - assertEquals(p3Power, pStatePowerModel.getInstantCpuPower()) - assertEquals(expectedP0P4Power, pStatePowerModel.getAccumulatedCpuPower()) - - verify(exactly = 4) { clock.millis() } - } -} diff --git a/simulator/opendc-experiments/opendc-experiments-capelin/src/main/kotlin/org/opendc/experiments/capelin/ExperimentHelpers.kt b/simulator/opendc-experiments/opendc-experiments-capelin/src/main/kotlin/org/opendc/experiments/capelin/ExperimentHelpers.kt index f327b55d..44436019 100644 --- a/simulator/opendc-experiments/opendc-experiments-capelin/src/main/kotlin/org/opendc/experiments/capelin/ExperimentHelpers.kt +++ b/simulator/opendc-experiments/opendc-experiments-capelin/src/main/kotlin/org/opendc/experiments/capelin/ExperimentHelpers.kt @@ -205,7 +205,7 @@ public fun attachMonitor( } .launchIn(coroutineScope) - (host as SimHost).powerDraw + (host as SimHost).machine.powerDraw .onEach { monitor.reportPowerConsumption(host, it) } .launchIn(coroutineScope) } diff --git a/simulator/opendc-format/src/main/kotlin/org/opendc/format/environment/MachineDef.kt b/simulator/opendc-format/src/main/kotlin/org/opendc/format/environment/MachineDef.kt index b5b3b84b..c7f7c4dc 100644 --- a/simulator/opendc-format/src/main/kotlin/org/opendc/format/environment/MachineDef.kt +++ b/simulator/opendc-format/src/main/kotlin/org/opendc/format/environment/MachineDef.kt @@ -22,8 +22,8 @@ package org.opendc.format.environment -import org.opendc.compute.simulator.power.api.CpuPowerModel import org.opendc.simulator.compute.SimMachineModel +import org.opendc.simulator.compute.power.MachinePowerModel import java.util.* public data class MachineDef( @@ -31,5 +31,5 @@ public data class MachineDef( val name: String, val meta: Map, val model: SimMachineModel, - val powerModel: CpuPowerModel + val powerModel: MachinePowerModel ) diff --git a/simulator/opendc-format/src/main/kotlin/org/opendc/format/environment/sc18/Sc18EnvironmentReader.kt b/simulator/opendc-format/src/main/kotlin/org/opendc/format/environment/sc18/Sc18EnvironmentReader.kt index 3da8d0b3..1f080c2d 100644 --- a/simulator/opendc-format/src/main/kotlin/org/opendc/format/environment/sc18/Sc18EnvironmentReader.kt +++ b/simulator/opendc-format/src/main/kotlin/org/opendc/format/environment/sc18/Sc18EnvironmentReader.kt @@ -25,13 +25,13 @@ package org.opendc.format.environment.sc18 import com.fasterxml.jackson.databind.ObjectMapper import com.fasterxml.jackson.module.kotlin.jacksonObjectMapper import com.fasterxml.jackson.module.kotlin.readValue -import org.opendc.compute.simulator.power.models.ConstantPowerModel import org.opendc.format.environment.EnvironmentReader import org.opendc.format.environment.MachineDef import org.opendc.simulator.compute.SimMachineModel 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.ConstantPowerModel import java.io.InputStream import java.util.* diff --git a/simulator/opendc-format/src/main/kotlin/org/opendc/format/environment/sc20/Sc20ClusterEnvironmentReader.kt b/simulator/opendc-format/src/main/kotlin/org/opendc/format/environment/sc20/Sc20ClusterEnvironmentReader.kt index 9a06a40f..cf90da68 100644 --- a/simulator/opendc-format/src/main/kotlin/org/opendc/format/environment/sc20/Sc20ClusterEnvironmentReader.kt +++ b/simulator/opendc-format/src/main/kotlin/org/opendc/format/environment/sc20/Sc20ClusterEnvironmentReader.kt @@ -22,13 +22,13 @@ package org.opendc.format.environment.sc20 -import org.opendc.compute.simulator.power.models.LinearPowerModel import org.opendc.format.environment.EnvironmentReader import org.opendc.format.environment.MachineDef import org.opendc.simulator.compute.SimMachineModel 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.LinearPowerModel import java.io.File import java.io.FileInputStream import java.io.InputStream diff --git a/simulator/opendc-format/src/main/kotlin/org/opendc/format/environment/sc20/Sc20EnvironmentReader.kt b/simulator/opendc-format/src/main/kotlin/org/opendc/format/environment/sc20/Sc20EnvironmentReader.kt index effd0286..c6a19430 100644 --- a/simulator/opendc-format/src/main/kotlin/org/opendc/format/environment/sc20/Sc20EnvironmentReader.kt +++ b/simulator/opendc-format/src/main/kotlin/org/opendc/format/environment/sc20/Sc20EnvironmentReader.kt @@ -25,13 +25,13 @@ package org.opendc.format.environment.sc20 import com.fasterxml.jackson.databind.ObjectMapper import com.fasterxml.jackson.module.kotlin.jacksonObjectMapper import com.fasterxml.jackson.module.kotlin.readValue -import org.opendc.compute.simulator.power.models.LinearPowerModel import org.opendc.format.environment.EnvironmentReader import org.opendc.format.environment.MachineDef import org.opendc.simulator.compute.SimMachineModel 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.LinearPowerModel import java.io.InputStream import java.util.* diff --git a/simulator/opendc-runner-web/src/main/kotlin/org/opendc/runner/web/TopologyParser.kt b/simulator/opendc-runner-web/src/main/kotlin/org/opendc/runner/web/TopologyParser.kt index e7e99a3d..1683cdb8 100644 --- a/simulator/opendc-runner-web/src/main/kotlin/org/opendc/runner/web/TopologyParser.kt +++ b/simulator/opendc-runner-web/src/main/kotlin/org/opendc/runner/web/TopologyParser.kt @@ -30,13 +30,13 @@ import com.mongodb.client.model.Filters import com.mongodb.client.model.Projections import org.bson.Document import org.bson.types.ObjectId -import org.opendc.compute.simulator.power.models.LinearPowerModel import org.opendc.format.environment.EnvironmentReader import org.opendc.format.environment.MachineDef import org.opendc.simulator.compute.SimMachineModel 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.LinearPowerModel import java.util.* /** diff --git a/simulator/opendc-simulator/opendc-simulator-compute/src/main/kotlin/org/opendc/simulator/compute/SimBareMetalMachine.kt b/simulator/opendc-simulator/opendc-simulator-compute/src/main/kotlin/org/opendc/simulator/compute/SimBareMetalMachine.kt index 19479719..f86c4198 100644 --- a/simulator/opendc-simulator/opendc-simulator-compute/src/main/kotlin/org/opendc/simulator/compute/SimBareMetalMachine.kt +++ b/simulator/opendc-simulator/opendc-simulator-compute/src/main/kotlin/org/opendc/simulator/compute/SimBareMetalMachine.kt @@ -23,7 +23,10 @@ package org.opendc.simulator.compute import kotlinx.coroutines.* +import kotlinx.coroutines.flow.* import org.opendc.simulator.compute.model.ProcessingUnit +import org.opendc.simulator.compute.power.ConstantPowerModel +import org.opendc.simulator.compute.power.MachinePowerModel import org.opendc.simulator.resources.* import org.opendc.utils.TimerScheduler import java.time.Clock @@ -43,14 +46,20 @@ import kotlin.coroutines.* public class SimBareMetalMachine( context: CoroutineContext, private val clock: Clock, - override val model: SimMachineModel + override val model: SimMachineModel, + private val powerModel: MachinePowerModel = ConstantPowerModel(0.0) ) : SimAbstractMachine(clock) { /** * The [Job] associated with this machine. */ - private val job = Job() + private val scope = CoroutineScope(context + Job()) - override val context: CoroutineContext = context + job + /** + * The power draw of the machine. + */ + public val powerDraw: StateFlow = usage.map { powerModel.computeCpuPower(it) }.stateIn(scope, SharingStarted.Eagerly, 0.0) + + override val context: CoroutineContext = scope.coroutineContext /** * The [TimerScheduler] to use for scheduling the interrupts. @@ -63,6 +72,6 @@ public class SimBareMetalMachine( override fun close() { super.close() scheduler.close() - job.cancel() + scope.cancel() } } diff --git a/simulator/opendc-simulator/opendc-simulator-compute/src/main/kotlin/org/opendc/simulator/compute/power/ConstantPowerModel.kt b/simulator/opendc-simulator/opendc-simulator-compute/src/main/kotlin/org/opendc/simulator/compute/power/ConstantPowerModel.kt new file mode 100644 index 00000000..5d7ae8ad --- /dev/null +++ b/simulator/opendc-simulator/opendc-simulator-compute/src/main/kotlin/org/opendc/simulator/compute/power/ConstantPowerModel.kt @@ -0,0 +1,8 @@ +package org.opendc.simulator.compute.power + +/** + * A power model which produces a constant value [constant]. + */ +public class ConstantPowerModel(private val constant: Double) : MachinePowerModel { + public override fun computeCpuPower(cpuUtil: Double): Double = constant +} diff --git a/simulator/opendc-simulator/opendc-simulator-compute/src/main/kotlin/org/opendc/simulator/compute/power/CubicPowerModel.kt b/simulator/opendc-simulator/opendc-simulator-compute/src/main/kotlin/org/opendc/simulator/compute/power/CubicPowerModel.kt new file mode 100644 index 00000000..8e47f571 --- /dev/null +++ b/simulator/opendc-simulator/opendc-simulator-compute/src/main/kotlin/org/opendc/simulator/compute/power/CubicPowerModel.kt @@ -0,0 +1,25 @@ +package org.opendc.simulator.compute.power + +import kotlin.math.pow + +/** + * The cubic power model partially adapted from CloudSim. + * + * @param maxPower The maximum power draw in Watts of the server. + * @param staticPowerPercent The static power percentage. + * @property staticPower The static power consumption that is not dependent on resource usage. + * It is the amount of energy consumed even when the host is idle. + * @property constPower The constant power consumption for each fraction of resource used. + */ +public class CubicPowerModel( + private var maxPower: Double, + staticPowerPercent: Double +) : MachinePowerModel { + private var staticPower: Double = staticPowerPercent * maxPower + private var constPower: Double = (maxPower - staticPower) / 100.0.pow(3) + + public override fun computeCpuPower(cpuUtil: Double): Double { + require(cpuUtil in 0.0..1.0) { "CPU utilization must be in [0, 1]" } + return staticPower + constPower * (cpuUtil * 100).pow(3) + } +} diff --git a/simulator/opendc-simulator/opendc-simulator-compute/src/main/kotlin/org/opendc/simulator/compute/power/InterpolationPowerModel.kt b/simulator/opendc-simulator/opendc-simulator-compute/src/main/kotlin/org/opendc/simulator/compute/power/InterpolationPowerModel.kt new file mode 100644 index 00000000..af7b6e6d --- /dev/null +++ b/simulator/opendc-simulator/opendc-simulator-compute/src/main/kotlin/org/opendc/simulator/compute/power/InterpolationPowerModel.kt @@ -0,0 +1,33 @@ +package org.opendc.simulator.compute.power + +import kotlin.math.ceil +import kotlin.math.floor + +/** + * The linear interpolation power model partially adapted from CloudSim. + * + * @param maxPower The maximum power draw in Watts of the server. + * @param staticPowerPercent The static power percentage. + * @property staticPower The static power consumption that is not dependent on resource usage. + * It is the amount of energy consumed even when the host is idle. + * @property constPower The constant power consumption for each fraction of resource used. + */ +public abstract class InterpolationPowerModel : MachinePowerModel { + + public override fun computeCpuPower(cpuUtil: Double): Double { + require(cpuUtil in 0.0..1.0) { "CPU utilization must be in [0, 1]" } + + val cpuUtilFlr = floor(cpuUtil * 10).toInt() + val cpuUtilCil = ceil(cpuUtil * 10).toInt() + val power1: Double = getPowerData(cpuUtilFlr) + val power2: Double = getPowerData(cpuUtilCil) + val delta = (power2 - power1) / 10 + + return if (cpuUtil % 0.1 == 0.0) + getPowerData((cpuUtil * 10).toInt()) + else + power1 + delta * (cpuUtil - cpuUtilFlr.toDouble() / 10) * 100 + } + + public abstract fun getPowerData(index: Int): Double +} diff --git a/simulator/opendc-simulator/opendc-simulator-compute/src/main/kotlin/org/opendc/simulator/compute/power/LinearPowerModel.kt b/simulator/opendc-simulator/opendc-simulator-compute/src/main/kotlin/org/opendc/simulator/compute/power/LinearPowerModel.kt new file mode 100644 index 00000000..14443aff --- /dev/null +++ b/simulator/opendc-simulator/opendc-simulator-compute/src/main/kotlin/org/opendc/simulator/compute/power/LinearPowerModel.kt @@ -0,0 +1,23 @@ +package org.opendc.simulator.compute.power + +/** + * The linear power model partially adapted from CloudSim. + * + * @param maxPower The maximum power draw in Watts of the server. + * @param staticPowerPercent The static power percentage. + * @property staticPower The static power consumption that is not dependent on resource usage. + * It is the amount of energy consumed even when the host is idle. + * @property constPower The constant power consumption for each fraction of resource used. + */ +public class LinearPowerModel( + private var maxPower: Double, + staticPowerPercent: Double +) : MachinePowerModel { + private var staticPower: Double = staticPowerPercent * maxPower + private var constPower: Double = (maxPower - staticPower) / 100 + + public override fun computeCpuPower(cpuUtil: Double): Double { + require(cpuUtil in 0.0..1.0) { "CPU utilization must be in [0, 1]" } + return staticPower + constPower * cpuUtil * 100 + } +} diff --git a/simulator/opendc-simulator/opendc-simulator-compute/src/main/kotlin/org/opendc/simulator/compute/power/MachinePowerModel.kt b/simulator/opendc-simulator/opendc-simulator-compute/src/main/kotlin/org/opendc/simulator/compute/power/MachinePowerModel.kt new file mode 100644 index 00000000..9bf03b87 --- /dev/null +++ b/simulator/opendc-simulator/opendc-simulator-compute/src/main/kotlin/org/opendc/simulator/compute/power/MachinePowerModel.kt @@ -0,0 +1,18 @@ +package org.opendc.simulator.compute.power + +import org.opendc.simulator.compute.SimMachine + +/** + * A model for estimating the power usage of a [SimMachine]. + */ +public interface MachinePowerModel { + /** + * Computes CPU power consumption for each host. + * + * @param cpuUtil The CPU utilization percentage. + * @return A [Double] value of CPU power consumption. + * @throws IllegalArgumentException Will throw an error if [cpuUtil] is out of range. + */ + @Throws(IllegalArgumentException::class) + public fun computeCpuPower(cpuUtil: Double): Double +} diff --git a/simulator/opendc-simulator/opendc-simulator-compute/src/main/kotlin/org/opendc/simulator/compute/power/PStatePowerModel.kt b/simulator/opendc-simulator/opendc-simulator-compute/src/main/kotlin/org/opendc/simulator/compute/power/PStatePowerModel.kt new file mode 100644 index 00000000..722f478d --- /dev/null +++ b/simulator/opendc-simulator/opendc-simulator-compute/src/main/kotlin/org/opendc/simulator/compute/power/PStatePowerModel.kt @@ -0,0 +1,96 @@ +package org.opendc.simulator.compute.power + +import org.opendc.simulator.compute.SimBareMetalMachine +import java.time.Clock +import java.util.* + +/** + * The CPU power model derived from the iCanCloud simulator. + * + * @param machine The [SimBareMetalMachine] that the model is measuring. + * @param clock The virtual [Clock] to track the time spent at each p-state. + * @property cpuPowerMeter A [MutableMap] that contains CPU frequencies ([Double]) in GHz + * as keys and the time ([Long]) spent in that frequency range in seconds. + * @property pStatesToPower A [TreeMap] that contains the frequency ([Double]) of corresponding p-state in GHz + * as keys and the energy ([Double]) consumption of that state in Watts. + * @property pStatesRange A [Pair] in which the fist and second elements are the lower and upper bounds of the + * consumption values of the p-states respectively. + * @property lastMeasureTime The last update time of the [cpuPowerMeter]. + * @property currPState The p-state that the model is currently in. + */ +public class PStatePowerModel( + private val machine: SimBareMetalMachine, + private val clock: Clock, +) { + // TODO: Extract the power meter out of the model. + private val cpuPowerMeter = mutableMapOf() + private val pStatesToPower = TreeMap() + private val pStatesRange: Pair + private var lastMeasureTime: Long + private var currPState: Double + + init { + loadPStates(this) + pStatesRange = Pair(pStatesToPower.keys.first(), pStatesToPower.keys.last()) + pStatesToPower.keys.forEach { cpuPowerMeter[it] = 0L } + currPState = pStatesRange.first + lastMeasureTime = getClockInstant() + updateCpuPowerMeter() + } + + /** Recorde the elapsed time to the corresponding p-state. */ + public fun updateCpuPowerMeter() { + val newMeasureTime = getClockInstant() + val newMaxFreq: Double = getMaxCpuSpeedInGHz() + assert(newMaxFreq in pStatesRange.first..pStatesRange.second) { + "The maximum frequency $newMaxFreq is not in the range of the P-state frequency " + + "from ${pStatesRange.first} to ${pStatesRange.second}." + } + + // Update the current p-state level on which the CPU is running. + val newPState = pStatesToPower.ceilingKey(newMaxFreq) + + // Add the time elapsed to the previous state. + cpuPowerMeter.merge(currPState, newMeasureTime - lastMeasureTime, Long::plus) + + // Update the current states. + currPState = newPState + lastMeasureTime = newMeasureTime + } + + /** Get the power value of the energy consumption level at which the CPU is working. */ + public fun getInstantCpuPower(): Double = + pStatesToPower.getOrDefault(currPState, 0.0) + + /** Get the accumulated power consumption up until now. */ + public fun getAccumulatedCpuPower(): Double = + pStatesToPower.keys + .map { + pStatesToPower.getOrDefault(it, 0.0) * + cpuPowerMeter.getOrDefault(it, 0.0).toDouble() + }.sum() + + private fun getClockInstant() = clock.millis() / 1000 + + /** Get the maximum frequency of the CPUs in GHz as that of the package. + * @see + * on page 34. + */ + private fun getMaxCpuSpeedInGHz() = (machine.speed.maxOrNull() ?: 0.0) / 1000 + + public companion object PStatesLoader { + private fun loadPStates(pStatePowerModel: PStatePowerModel) { + // TODO: Dynamically load configuration. + // See P4 of https://www.intel.com/content/dam/support/us/en/documents/motherboards/server/sb/power_management_of_intel_architecture_servers.pdf + pStatePowerModel.pStatesToPower.putAll( + sortedMapOf( + 3.6 to 103.0, + 3.4 to 94.0, + 3.2 to 85.0, + 3.0 to 76.0, + 2.8 to 8.0, + ) + ) + } + } +} diff --git a/simulator/opendc-simulator/opendc-simulator-compute/src/main/kotlin/org/opendc/simulator/compute/power/SqrtPowerModel.kt b/simulator/opendc-simulator/opendc-simulator-compute/src/main/kotlin/org/opendc/simulator/compute/power/SqrtPowerModel.kt new file mode 100644 index 00000000..bf177aff --- /dev/null +++ b/simulator/opendc-simulator/opendc-simulator-compute/src/main/kotlin/org/opendc/simulator/compute/power/SqrtPowerModel.kt @@ -0,0 +1,25 @@ +package org.opendc.simulator.compute.power + +import kotlin.math.sqrt + +/** + * The square root power model partially adapted from CloudSim. + * + * @param maxPower The maximum power draw in Watts of the server. + * @param staticPowerPercent The static power percentage. + * @property staticPower The static power consumption that is not dependent on resource usage. + * It is the amount of energy consumed even when the host is idle. + * @property constPower The constant power consumption for each fraction of resource used. + */ +public class SqrtPowerModel( + private var maxPower: Double, + staticPowerPercent: Double +) : MachinePowerModel { + private var staticPower: Double = staticPowerPercent * maxPower + private var constPower: Double = (maxPower - staticPower) / sqrt(100.0) + + override fun computeCpuPower(cpuUtil: Double): Double { + require(cpuUtil in 0.0..1.0) { "CPU utilization must be in [0, 1]" } + return staticPower + constPower * sqrt(cpuUtil * 100) + } +} diff --git a/simulator/opendc-simulator/opendc-simulator-compute/src/main/kotlin/org/opendc/simulator/compute/power/SquarePowerModel.kt b/simulator/opendc-simulator/opendc-simulator-compute/src/main/kotlin/org/opendc/simulator/compute/power/SquarePowerModel.kt new file mode 100644 index 00000000..cbfad530 --- /dev/null +++ b/simulator/opendc-simulator/opendc-simulator-compute/src/main/kotlin/org/opendc/simulator/compute/power/SquarePowerModel.kt @@ -0,0 +1,25 @@ +package org.opendc.simulator.compute.power + +import kotlin.math.pow + +/** + * The square power model partially adapted from CloudSim. + * + * @param maxPower The maximum power draw in Watts of the server. + * @param staticPowerPercent The static power percentage. + * @property staticPower The static power consumption that is not dependent on resource usage. + * It is the amount of energy consumed even when the host is idle. + * @property constPower The constant power consumption for each fraction of resource used. + */ +public class SquarePowerModel( + private var maxPower: Double, + staticPowerPercent: Double +) : MachinePowerModel { + private var staticPower: Double = staticPowerPercent * maxPower + private var constPower: Double = (maxPower - staticPower) / 100.0.pow(2) + + override fun computeCpuPower(cpuUtil: Double): Double { + require(cpuUtil in 0.0..1.0) { "CPU utilization must be in [0, 1]" } + return staticPower + constPower * (cpuUtil * 100).pow(2) + } +} diff --git a/simulator/opendc-simulator/opendc-simulator-compute/src/main/kotlin/org/opendc/simulator/compute/power/ZeroIdlePowerDecorator.kt b/simulator/opendc-simulator/opendc-simulator-compute/src/main/kotlin/org/opendc/simulator/compute/power/ZeroIdlePowerDecorator.kt new file mode 100644 index 00000000..01deac5b --- /dev/null +++ b/simulator/opendc-simulator/opendc-simulator-compute/src/main/kotlin/org/opendc/simulator/compute/power/ZeroIdlePowerDecorator.kt @@ -0,0 +1,12 @@ +package org.opendc.simulator.compute.power + +/** + * A decorator for ignoring the idle power when computing energy consumption of components. + * + * @param delegate The [MachinePowerModel] to delegate to. + */ +public class ZeroIdlePowerDecorator(private val delegate: MachinePowerModel) : MachinePowerModel { + override fun computeCpuPower(cpuUtil: Double): Double { + return if (cpuUtil == 0.0) 0.0 else delegate.computeCpuPower(cpuUtil) + } +} diff --git a/simulator/opendc-simulator/opendc-simulator-compute/src/test/kotlin/org/opendc/simulator/compute/power/MachinePowerModelTest.kt b/simulator/opendc-simulator/opendc-simulator-compute/src/test/kotlin/org/opendc/simulator/compute/power/MachinePowerModelTest.kt new file mode 100644 index 00000000..9fdd0363 --- /dev/null +++ b/simulator/opendc-simulator/opendc-simulator-compute/src/test/kotlin/org/opendc/simulator/compute/power/MachinePowerModelTest.kt @@ -0,0 +1,50 @@ +package org.opendc.simulator.compute.power + +import io.mockk.* +import kotlinx.coroutines.ExperimentalCoroutinesApi +import kotlinx.coroutines.flow.* +import org.junit.jupiter.api.Assertions.assertEquals +import org.junit.jupiter.params.ParameterizedTest +import org.junit.jupiter.params.provider.Arguments +import org.junit.jupiter.params.provider.MethodSource +import java.util.stream.Stream +import kotlin.math.pow + +@OptIn(ExperimentalCoroutinesApi::class) +internal class MachinePowerModelTest { + private val epsilon = 10.0.pow(-3) + private val cpuUtil = 0.9 + + @ParameterizedTest + @MethodSource("MachinePowerModelArgs") + fun `compute power consumption given CPU loads`( + powerModel: MachinePowerModel, + expectedPowerConsumption: Double + ) { + val computedPowerConsumption = powerModel.computeCpuPower(cpuUtil) + assertEquals(expectedPowerConsumption, computedPowerConsumption, epsilon) + } + + @ParameterizedTest + @MethodSource("MachinePowerModelArgs") + fun `ignore idle power when computing power consumptions`( + powerModel: MachinePowerModel, + expectedPowerConsumption: Double + ) { + val zeroPowerModel = ZeroIdlePowerDecorator(powerModel) + val computedPowerConsumption = zeroPowerModel.computeCpuPower(0.0) + assertEquals(0.0, computedPowerConsumption) + } + + @Suppress("unused") + private companion object { + @JvmStatic + fun MachinePowerModelArgs(): Stream = Stream.of( + Arguments.of(ConstantPowerModel(0.0), 0.0), + Arguments.of(LinearPowerModel(350.0, 200 / 350.0), 335.0), + Arguments.of(SquarePowerModel(350.0, 200 / 350.0), 321.5), + Arguments.of(CubicPowerModel(350.0, 200 / 350.0), 309.35), + Arguments.of(SqrtPowerModel(350.0, 200 / 350.0), 342.302), + ) + } +} diff --git a/simulator/opendc-simulator/opendc-simulator-compute/src/test/kotlin/org/opendc/simulator/compute/power/PStatePowerModelTest.kt b/simulator/opendc-simulator/opendc-simulator-compute/src/test/kotlin/org/opendc/simulator/compute/power/PStatePowerModelTest.kt new file mode 100644 index 00000000..9116f928 --- /dev/null +++ b/simulator/opendc-simulator/opendc-simulator-compute/src/test/kotlin/org/opendc/simulator/compute/power/PStatePowerModelTest.kt @@ -0,0 +1,44 @@ +package org.opendc.simulator.compute.power + +import io.mockk.* +import org.junit.jupiter.api.Assertions.assertEquals +import org.junit.jupiter.api.Test +import org.opendc.simulator.compute.SimBareMetalMachine +import java.time.Clock + +internal class PStatePowerModelTest { + @Test + fun `update CPU power meter with P-states`() { + val p0Power = 8.0 + val p3Power = 94.0 + val p4Power = 103.0 + val expectedP0Power = 8.0 * 10 + val expectedP0P4Power = expectedP0Power + 103.0 * 10 + + val clock = mockkClass(Clock::class) + val machine = mockkClass(SimBareMetalMachine::class) + every { clock.millis() } returnsMany listOf(0L, 0L, 10_000L, 20_000L) + every { machine.speed } returns + listOf(2.8, 2.8, 2.8, 2.8).map { it * 1000 } andThen // Max. 2.8MHz covered by P0 + listOf(1.5, 3.1, 3.3, 3.6).map { it * 1000 } andThen // Max. 3.6MHz covered by P4 + listOf(1.5, 3.1, 3.1, 3.3).map { it * 1000 } // Max. 3.3MHz covered by P3 + + // Power meter initialization. + val pStatePowerModel = PStatePowerModel(machine, clock) + verify(exactly = 2) { clock.millis() } + verify(exactly = 1) { machine.speed } + assertEquals(p0Power, pStatePowerModel.getInstantCpuPower()) + + // The first measure. + pStatePowerModel.updateCpuPowerMeter() + assertEquals(p4Power, pStatePowerModel.getInstantCpuPower()) + assertEquals(expectedP0Power, pStatePowerModel.getAccumulatedCpuPower()) + + // The second measure. + pStatePowerModel.updateCpuPowerMeter() + assertEquals(p3Power, pStatePowerModel.getInstantCpuPower()) + assertEquals(expectedP0P4Power, pStatePowerModel.getAccumulatedCpuPower()) + + verify(exactly = 4) { clock.millis() } + } +} -- cgit v1.2.3