diff options
| author | Hongyu <hongyuhe.cs@googlemail.com> | 2021-03-16 09:08:51 +0100 |
|---|---|---|
| committer | Fabian Mastenbroek <mail.fabianm@gmail.com> | 2021-03-18 11:37:43 +0100 |
| commit | dd24782f3710678151c80f8ad365eecc7389b6f8 (patch) | |
| tree | 6e5ddf4e61e70495d67d4220f7657e7b271301e8 /simulator/opendc-compute/opendc-compute-simulator/src/main | |
| parent | 054a3d376b8b31ba98f91e7b34c6e0ca717def18 (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.
Diffstat (limited to 'simulator/opendc-compute/opendc-compute-simulator/src/main')
3 files changed, 101 insertions, 8 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, + ) + ) + } + } +} |
