summaryrefslogtreecommitdiff
path: root/simulator/opendc-simulator
diff options
context:
space:
mode:
authorFabian Mastenbroek <mail.fabianm@gmail.com>2021-03-24 13:05:33 +0100
committerFabian Mastenbroek <mail.fabianm@gmail.com>2021-03-24 13:05:33 +0100
commit69598598be2c248acc49e40607b3dd0998ec1ca5 (patch)
tree62e154b6cc5b22f79f3aa67cf245c40c1332f7b4 /simulator/opendc-simulator
parent0ad600eb8e14c0ef3ba5529c59d300dc20c85ab2 (diff)
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.
Diffstat (limited to 'simulator/opendc-simulator')
-rw-r--r--simulator/opendc-simulator/opendc-simulator-compute/src/main/kotlin/org/opendc/simulator/compute/SimBareMetalMachine.kt17
-rw-r--r--simulator/opendc-simulator/opendc-simulator-compute/src/main/kotlin/org/opendc/simulator/compute/power/ConstantPowerModel.kt8
-rw-r--r--simulator/opendc-simulator/opendc-simulator-compute/src/main/kotlin/org/opendc/simulator/compute/power/CubicPowerModel.kt25
-rw-r--r--simulator/opendc-simulator/opendc-simulator-compute/src/main/kotlin/org/opendc/simulator/compute/power/InterpolationPowerModel.kt33
-rw-r--r--simulator/opendc-simulator/opendc-simulator-compute/src/main/kotlin/org/opendc/simulator/compute/power/LinearPowerModel.kt23
-rw-r--r--simulator/opendc-simulator/opendc-simulator-compute/src/main/kotlin/org/opendc/simulator/compute/power/MachinePowerModel.kt18
-rw-r--r--simulator/opendc-simulator/opendc-simulator-compute/src/main/kotlin/org/opendc/simulator/compute/power/PStatePowerModel.kt96
-rw-r--r--simulator/opendc-simulator/opendc-simulator-compute/src/main/kotlin/org/opendc/simulator/compute/power/SqrtPowerModel.kt25
-rw-r--r--simulator/opendc-simulator/opendc-simulator-compute/src/main/kotlin/org/opendc/simulator/compute/power/SquarePowerModel.kt25
-rw-r--r--simulator/opendc-simulator/opendc-simulator-compute/src/main/kotlin/org/opendc/simulator/compute/power/ZeroIdlePowerDecorator.kt12
-rw-r--r--simulator/opendc-simulator/opendc-simulator-compute/src/test/kotlin/org/opendc/simulator/compute/power/MachinePowerModelTest.kt50
-rw-r--r--simulator/opendc-simulator/opendc-simulator-compute/src/test/kotlin/org/opendc/simulator/compute/power/PStatePowerModelTest.kt44
12 files changed, 372 insertions, 4 deletions
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<Double> = 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<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-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<Arguments> = 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() }
+ }
+}