summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorHongyu <hongyuhe.cs@googlemail.com>2021-03-16 09:08:51 +0100
committerFabian Mastenbroek <mail.fabianm@gmail.com>2021-03-18 11:37:43 +0100
commitdd24782f3710678151c80f8ad365eecc7389b6f8 (patch)
tree6e5ddf4e61e70495d67d4220f7657e7b271301e8
parent054a3d376b8b31ba98f91e7b34c6e0ca717def18 (diff)
simulator: Add the CPU power model from iCanCloud/E-mc2
This change implements the CPU energy model with p-states from iCanCloud/E-mc2: - Only pushed a portion of the code for discussion as not sure if the idea is on track. - Inline comments have been added, and formal documents will follow once the model is finalized. - The p-state power consumptions are currently hard-coded in a companion object, which should be improved in the next PR(s). **Breaking Changes** - CpuPowerModel: directly interact with the machine it is measuring. - SimBareMetalMachine: expose the speeds of its CPU cores and its clock instant.
-rw-r--r--simulator/opendc-compute/opendc-compute-simulator/src/main/kotlin/org/opendc/compute/simulator/SimHost.kt2
-rw-r--r--simulator/opendc-compute/opendc-compute-simulator/src/main/kotlin/org/opendc/compute/simulator/power/api/CpuPowerModel.kt11
-rw-r--r--simulator/opendc-compute/opendc-compute-simulator/src/main/kotlin/org/opendc/compute/simulator/power/models/PStatePowerModel.kt96
-rw-r--r--simulator/opendc-compute/opendc-compute-simulator/src/test/kotlin/org/opendc/compute/simulator/power/CpuPowerModelTest.kt8
-rw-r--r--simulator/opendc-compute/opendc-compute-simulator/src/test/kotlin/org/opendc/compute/simulator/power/PStatePowerModelTest.kt45
-rw-r--r--simulator/opendc-simulator/opendc-simulator-compute/src/main/kotlin/org/opendc/simulator/compute/SimAbstractMachine.kt14
6 files changed, 159 insertions, 17 deletions
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 9cc1bf54..6e9b8151 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
@@ -133,7 +133,7 @@ public class SimHost(
override val model: HostModel = HostModel(model.cpus.size, model.memory.map { it.size }.sum())
- override val powerDraw: Flow<Double> = cpuPowerModel.getPowerDraw(this)
+ override val powerDraw: Flow<Double> = cpuPowerModel.getPowerDraw(machine)
init {
// Launch hypervisor onto machine
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
index 604b69c0..893f7ab1 100644
--- 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
@@ -2,7 +2,7 @@ package org.opendc.compute.simulator.power.api
import kotlinx.coroutines.flow.Flow
import kotlinx.coroutines.flow.map
-import org.opendc.compute.simulator.SimHost
+import org.opendc.simulator.compute.SimMachine
public interface CpuPowerModel {
/**
@@ -18,14 +18,11 @@ public interface CpuPowerModel {
/**
* Emits the values of power consumption for servers.
*
- * @param host A [SimHost] that offers host CPU utilization.
- * @param withoutIdle A [Boolean] flag indicates whether (false) add a constant
- * power consumption value when the server is idle or (true) not
- * with a default value being false.
+ * @param machine The [SimMachine] that the model is measuring.
* @return A [Flow] of values representing the server power draw.
*/
- public fun getPowerDraw(host: SimHost, withoutIdle: Boolean = false): Flow<Double> =
- host.machine.usage.map {
+ public fun getPowerDraw(machine: SimMachine): Flow<Double> =
+ machine.usage.map {
computeCpuPower(it)
}
}
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
new file mode 100644
index 00000000..aea089da
--- /dev/null
+++ b/simulator/opendc-compute/opendc-compute-simulator/src/main/kotlin/org/opendc/compute/simulator/power/models/PStatePowerModel.kt
@@ -0,0 +1,96 @@
+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<Double, Long>()
+ private val pStatesToPower = TreeMap<Double, Double>()
+ private val pStatesRange: Pair<Double, Double>
+ 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 <a href="https://www.intel.vn/content/dam/www/public/us/en/documents/datasheets/10th-gen-core-families-datasheet-vol-1-datasheet.pdf">
+ * 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/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
index 9d034a5d..cd18b120 100644
--- 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
@@ -8,7 +8,6 @@ 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.SimHost
import org.opendc.compute.simulator.power.api.CpuPowerModel
import org.opendc.compute.simulator.power.models.*
import org.opendc.simulator.compute.SimBareMetalMachine
@@ -18,7 +17,7 @@ import kotlin.math.pow
@OptIn(ExperimentalCoroutinesApi::class)
internal class CpuPowerModelTest {
private val epsilon = 10.0.pow(-3)
- private val cpuUtil = .9
+ private val cpuUtil = 0.9
@ParameterizedTest
@MethodSource("cpuPowerModelArgs")
@@ -49,12 +48,10 @@ internal class CpuPowerModelTest {
) {
runBlockingTest {
val cpuLoads = flowOf(cpuUtil, cpuUtil, cpuUtil).stateIn(this)
- val bareMetalDriver = mockkClass(SimHost::class)
val machine = mockkClass(SimBareMetalMachine::class)
- every { bareMetalDriver.machine } returns machine
every { machine.usage } returns cpuLoads
- val serverPowerDraw = powerModel.getPowerDraw(bareMetalDriver)
+ val serverPowerDraw = powerModel.getPowerDraw(machine)
assertEquals(
serverPowerDraw.first().toDouble(),
@@ -62,7 +59,6 @@ internal class CpuPowerModelTest {
epsilon
)
- verify(exactly = 1) { bareMetalDriver.machine }
verify(exactly = 1) { machine.usage }
}
}
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
new file mode 100644
index 00000000..e144e541
--- /dev/null
+++ b/simulator/opendc-compute/opendc-compute-simulator/src/test/kotlin/org/opendc/compute/simulator/power/PStatePowerModelTest.kt
@@ -0,0 +1,45 @@
+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-simulator/opendc-simulator-compute/src/main/kotlin/org/opendc/simulator/compute/SimAbstractMachine.kt b/simulator/opendc-simulator/opendc-simulator-compute/src/main/kotlin/org/opendc/simulator/compute/SimAbstractMachine.kt
index 1bdbb7e8..39ae34fe 100644
--- a/simulator/opendc-simulator/opendc-simulator-compute/src/main/kotlin/org/opendc/simulator/compute/SimAbstractMachine.kt
+++ b/simulator/opendc-simulator/opendc-simulator-compute/src/main/kotlin/org/opendc/simulator/compute/SimAbstractMachine.kt
@@ -38,8 +38,6 @@ import kotlin.coroutines.CoroutineContext
/**
* Abstract implementation of the [SimMachine] interface.
- *
- * @param context The [CoroutineContext] in which the machine runs.
*/
public abstract class SimAbstractMachine(private val clock: Clock) : SimMachine {
private val _usage = MutableStateFlow(0.0)
@@ -47,6 +45,13 @@ public abstract class SimAbstractMachine(private val clock: Clock) : SimMachine
get() = _usage
/**
+ * The speed of the CPU cores.
+ */
+ public val speed: List<Double>
+ get() = _speed
+ private var _speed = mutableListOf<Double>()
+
+ /**
* A flag to indicate that the machine is terminated.
*/
private var isTerminated = false
@@ -89,13 +94,16 @@ public abstract class SimAbstractMachine(private val clock: Clock) : SimMachine
val ctx = Context(resources, meta + mapOf("coroutine-context" to context))
val totalCapacity = model.cpus.sumByDouble { it.frequency }
+ _speed = MutableList(model.cpus.size) { 0.0 }
+
workload.onStart(ctx)
for ((cpu, source) in resources) {
val consumer = workload.getConsumer(ctx, cpu)
val job = source.speed
.onEach {
- _usage.value = resources.values.sumByDouble { it.speed.value } / totalCapacity
+ _speed[cpu.id] = source.speed.value
+ _usage.value = _speed.sum() / totalCapacity
}
.launchIn(this)