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 (limited to 'simulator/opendc-simulator/opendc-simulator-compute') 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. --- .../main/kotlin/org/opendc/simulator/compute/SimAbstractHypervisor.kt | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) (limited to 'simulator/opendc-simulator/opendc-simulator-compute') 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 } } -- 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. --- .../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 ++++++++++ 12 files changed, 372 insertions(+), 4 deletions(-) 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 (limited to 'simulator/opendc-simulator/opendc-simulator-compute') 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