summaryrefslogtreecommitdiff
path: root/opendc-simulator/opendc-simulator-compute/src/test
diff options
context:
space:
mode:
Diffstat (limited to 'opendc-simulator/opendc-simulator-compute/src/test')
-rw-r--r--opendc-simulator/opendc-simulator-compute/src/test/kotlin/org/opendc/simulator/compute/SimHypervisorTest.kt198
-rw-r--r--opendc-simulator/opendc-simulator-compute/src/test/kotlin/org/opendc/simulator/compute/SimMachineTest.kt116
-rw-r--r--opendc-simulator/opendc-simulator-compute/src/test/kotlin/org/opendc/simulator/compute/SimSpaceSharedHypervisorTest.kt227
-rw-r--r--opendc-simulator/opendc-simulator-compute/src/test/kotlin/org/opendc/simulator/compute/cpufreq/DemandScalingGovernorTest.kt48
-rw-r--r--opendc-simulator/opendc-simulator-compute/src/test/kotlin/org/opendc/simulator/compute/cpufreq/PStateScalingDriverTest.kt132
-rw-r--r--opendc-simulator/opendc-simulator-compute/src/test/kotlin/org/opendc/simulator/compute/power/PowerModelTest.kt70
-rw-r--r--opendc-simulator/opendc-simulator-compute/src/test/kotlin/org/opendc/simulator/compute/workload/SimFlopsWorkloadTest.kt59
-rw-r--r--opendc-simulator/opendc-simulator-compute/src/test/resources/spec_machines.yml29
8 files changed, 879 insertions, 0 deletions
diff --git a/opendc-simulator/opendc-simulator-compute/src/test/kotlin/org/opendc/simulator/compute/SimHypervisorTest.kt b/opendc-simulator/opendc-simulator-compute/src/test/kotlin/org/opendc/simulator/compute/SimHypervisorTest.kt
new file mode 100644
index 00000000..a067dd2e
--- /dev/null
+++ b/opendc-simulator/opendc-simulator-compute/src/test/kotlin/org/opendc/simulator/compute/SimHypervisorTest.kt
@@ -0,0 +1,198 @@
+/*
+ * 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.simulator.compute
+
+import kotlinx.coroutines.ExperimentalCoroutinesApi
+import kotlinx.coroutines.coroutineScope
+import kotlinx.coroutines.flow.toList
+import kotlinx.coroutines.launch
+import kotlinx.coroutines.yield
+import org.junit.jupiter.api.Assertions.assertEquals
+import org.junit.jupiter.api.BeforeEach
+import org.junit.jupiter.api.Test
+import org.junit.jupiter.api.assertAll
+import org.opendc.simulator.compute.cpufreq.PerformanceScalingGovernor
+import org.opendc.simulator.compute.cpufreq.SimpleScalingDriver
+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 org.opendc.simulator.compute.workload.SimTraceWorkload
+import org.opendc.simulator.core.runBlockingSimulation
+
+/**
+ * Test suite for the [SimHypervisor] class.
+ */
+@OptIn(ExperimentalCoroutinesApi::class)
+internal class SimHypervisorTest {
+ private lateinit var model: SimMachineModel
+
+ @BeforeEach
+ fun setUp() {
+ val cpuNode = ProcessingNode("Intel", "Xeon", "amd64", 1)
+ model = SimMachineModel(
+ cpus = List(cpuNode.coreCount) { ProcessingUnit(cpuNode, it, 3200.0) },
+ memory = List(4) { MemoryUnit("Crucial", "MTA18ASF4G72AZ-3G2B1", 3200.0, 32_000) }
+ )
+ }
+
+ /**
+ * Test overcommitting of resources via the hypervisor with a single VM.
+ */
+ @Test
+ fun testOvercommittedSingle() = runBlockingSimulation {
+ val listener = object : SimHypervisor.Listener {
+ var totalRequestedWork = 0L
+ var totalGrantedWork = 0L
+ var totalOvercommittedWork = 0L
+
+ override fun onSliceFinish(
+ hypervisor: SimHypervisor,
+ requestedWork: Long,
+ grantedWork: Long,
+ overcommittedWork: Long,
+ interferedWork: Long,
+ cpuUsage: Double,
+ cpuDemand: Double
+ ) {
+ totalRequestedWork += requestedWork
+ totalGrantedWork += grantedWork
+ totalOvercommittedWork += overcommittedWork
+ }
+ }
+
+ val duration = 5 * 60L
+ val workloadA =
+ SimTraceWorkload(
+ sequenceOf(
+ SimTraceWorkload.Fragment(duration * 1000, 28.0, 1),
+ SimTraceWorkload.Fragment(duration * 1000, 3500.0, 1),
+ SimTraceWorkload.Fragment(duration * 1000, 0.0, 1),
+ SimTraceWorkload.Fragment(duration * 1000, 183.0, 1)
+ ),
+ )
+
+ val machine = SimBareMetalMachine(coroutineContext, clock, model, PerformanceScalingGovernor(), SimpleScalingDriver(ConstantPowerModel(0.0)))
+ val hypervisor = SimFairShareHypervisor(listener)
+
+ launch {
+ machine.run(hypervisor)
+ println("Hypervisor finished")
+ }
+ yield()
+ val vm = hypervisor.createMachine(model)
+ val res = mutableListOf<Double>()
+ val job = launch { machine.usage.toList(res) }
+
+ vm.run(workloadA)
+ yield()
+ job.cancel()
+ machine.close()
+
+ assertAll(
+ { assertEquals(1113300, listener.totalRequestedWork, "Requested Burst does not match") },
+ { assertEquals(1023300, listener.totalGrantedWork, "Granted Burst does not match") },
+ { assertEquals(90000, listener.totalOvercommittedWork, "Overcommissioned Burst does not match") },
+ { assertEquals(listOf(0.0, 0.00875, 1.0, 0.0, 0.0571875, 0.0), res) { "VM usage is correct" } },
+ { assertEquals(1200000, clock.millis()) { "Current time is correct" } }
+ )
+ }
+
+ /**
+ * Test overcommitting of resources via the hypervisor with two VMs.
+ */
+ @Test
+ fun testOvercommittedDual() = runBlockingSimulation {
+ val listener = object : SimHypervisor.Listener {
+ var totalRequestedWork = 0L
+ var totalGrantedWork = 0L
+ var totalOvercommittedWork = 0L
+
+ override fun onSliceFinish(
+ hypervisor: SimHypervisor,
+ requestedWork: Long,
+ grantedWork: Long,
+ overcommittedWork: Long,
+ interferedWork: Long,
+ cpuUsage: Double,
+ cpuDemand: Double
+ ) {
+ totalRequestedWork += requestedWork
+ totalGrantedWork += grantedWork
+ totalOvercommittedWork += overcommittedWork
+ }
+ }
+
+ val duration = 5 * 60L
+ val workloadA =
+ SimTraceWorkload(
+ sequenceOf(
+ SimTraceWorkload.Fragment(duration * 1000, 28.0, 1),
+ SimTraceWorkload.Fragment(duration * 1000, 3500.0, 1),
+ SimTraceWorkload.Fragment(duration * 1000, 0.0, 1),
+ SimTraceWorkload.Fragment(duration * 1000, 183.0, 1)
+ ),
+ )
+ val workloadB =
+ SimTraceWorkload(
+ sequenceOf(
+ SimTraceWorkload.Fragment(duration * 1000, 28.0, 1),
+ SimTraceWorkload.Fragment(duration * 1000, 3100.0, 1),
+ SimTraceWorkload.Fragment(duration * 1000, 0.0, 1),
+ SimTraceWorkload.Fragment(duration * 1000, 73.0, 1)
+ )
+ )
+
+ val machine = SimBareMetalMachine(
+ coroutineContext, clock, model, PerformanceScalingGovernor(),
+ SimpleScalingDriver(ConstantPowerModel(0.0))
+ )
+ val hypervisor = SimFairShareHypervisor(listener)
+
+ launch {
+ machine.run(hypervisor)
+ }
+
+ yield()
+ coroutineScope {
+ launch {
+ val vm = hypervisor.createMachine(model)
+ vm.run(workloadA)
+ vm.close()
+ }
+ val vm = hypervisor.createMachine(model)
+ vm.run(workloadB)
+ vm.close()
+ }
+ yield()
+ machine.close()
+ yield()
+
+ assertAll(
+ { assertEquals(2082000, listener.totalRequestedWork, "Requested Burst does not match") },
+ { assertEquals(1062000, listener.totalGrantedWork, "Granted Burst does not match") },
+ { assertEquals(1020000, listener.totalOvercommittedWork, "Overcommissioned Burst does not match") },
+ { assertEquals(1200000, clock.millis()) }
+ )
+ }
+}
diff --git a/opendc-simulator/opendc-simulator-compute/src/test/kotlin/org/opendc/simulator/compute/SimMachineTest.kt b/opendc-simulator/opendc-simulator-compute/src/test/kotlin/org/opendc/simulator/compute/SimMachineTest.kt
new file mode 100644
index 00000000..205f2eca
--- /dev/null
+++ b/opendc-simulator/opendc-simulator-compute/src/test/kotlin/org/opendc/simulator/compute/SimMachineTest.kt
@@ -0,0 +1,116 @@
+/*
+ * 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.simulator.compute
+
+import kotlinx.coroutines.*
+import kotlinx.coroutines.flow.toList
+import org.junit.jupiter.api.Assertions.assertEquals
+import org.junit.jupiter.api.BeforeEach
+import org.junit.jupiter.api.Test
+import org.junit.jupiter.api.assertDoesNotThrow
+import org.junit.jupiter.api.assertThrows
+import org.opendc.simulator.compute.cpufreq.PerformanceScalingGovernor
+import org.opendc.simulator.compute.cpufreq.SimpleScalingDriver
+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 org.opendc.simulator.compute.workload.SimFlopsWorkload
+import org.opendc.simulator.core.runBlockingSimulation
+
+/**
+ * Test suite for the [SimBareMetalMachine] class.
+ */
+@OptIn(ExperimentalCoroutinesApi::class)
+class SimMachineTest {
+ private lateinit var machineModel: SimMachineModel
+
+ @BeforeEach
+ fun setUp() {
+ 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) }
+ )
+ }
+
+ @Test
+ fun testFlopsWorkload() = runBlockingSimulation {
+ val machine = SimBareMetalMachine(coroutineContext, clock, machineModel, PerformanceScalingGovernor(), SimpleScalingDriver(ConstantPowerModel(0.0)))
+
+ try {
+ machine.run(SimFlopsWorkload(2_000, utilization = 1.0))
+
+ // Two cores execute 1000 MFlOps per second (1000 ms)
+ assertEquals(1000, clock.millis())
+ } finally {
+ machine.close()
+ }
+ }
+
+ @Test
+ fun testDualSocketMachine() = runBlockingSimulation {
+ val cpuNode = machineModel.cpus[0].node
+ val machineModel = SimMachineModel(
+ cpus = List(cpuNode.coreCount * 2) { ProcessingUnit(cpuNode, it % 2, 1000.0) },
+ memory = List(4) { MemoryUnit("Crucial", "MTA18ASF4G72AZ-3G2B1", 3200.0, 32_000) }
+ )
+ val machine = SimBareMetalMachine(coroutineContext, clock, machineModel, PerformanceScalingGovernor(), SimpleScalingDriver(ConstantPowerModel(0.0)))
+
+ try {
+ machine.run(SimFlopsWorkload(2_000, utilization = 1.0))
+
+ // Two sockets with two cores execute 2000 MFlOps per second (500 ms)
+ assertEquals(500, clock.millis())
+ } finally {
+ machine.close()
+ }
+ }
+
+ @Test
+ fun testUsage() = runBlockingSimulation {
+ val machine = SimBareMetalMachine(coroutineContext, clock, machineModel, PerformanceScalingGovernor(), SimpleScalingDriver(ConstantPowerModel(0.0)))
+
+ val res = mutableListOf<Double>()
+ val job = launch { machine.usage.toList(res) }
+
+ try {
+ machine.run(SimFlopsWorkload(2_000, utilization = 1.0))
+ yield()
+ job.cancel()
+ assertEquals(listOf(0.0, 0.5, 1.0, 0.5, 0.0), res) { "Machine is fully utilized" }
+ } finally {
+ machine.close()
+ }
+ }
+
+ @Test
+ fun testClose() = runBlockingSimulation {
+ val machine = SimBareMetalMachine(coroutineContext, clock, machineModel, PerformanceScalingGovernor(), SimpleScalingDriver(ConstantPowerModel(0.0)))
+
+ machine.close()
+ assertDoesNotThrow { machine.close() }
+ assertThrows<IllegalStateException> { machine.run(SimFlopsWorkload(2_000, utilization = 1.0)) }
+ }
+}
diff --git a/opendc-simulator/opendc-simulator-compute/src/test/kotlin/org/opendc/simulator/compute/SimSpaceSharedHypervisorTest.kt b/opendc-simulator/opendc-simulator-compute/src/test/kotlin/org/opendc/simulator/compute/SimSpaceSharedHypervisorTest.kt
new file mode 100644
index 00000000..ef6f536d
--- /dev/null
+++ b/opendc-simulator/opendc-simulator-compute/src/test/kotlin/org/opendc/simulator/compute/SimSpaceSharedHypervisorTest.kt
@@ -0,0 +1,227 @@
+/*
+ * 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.flow.toList
+import kotlinx.coroutines.launch
+import kotlinx.coroutines.yield
+import org.junit.jupiter.api.Assertions.*
+import org.junit.jupiter.api.BeforeEach
+import org.junit.jupiter.api.Test
+import org.junit.jupiter.api.assertThrows
+import org.opendc.simulator.compute.cpufreq.PerformanceScalingGovernor
+import org.opendc.simulator.compute.cpufreq.SimpleScalingDriver
+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 org.opendc.simulator.compute.workload.SimFlopsWorkload
+import org.opendc.simulator.compute.workload.SimRuntimeWorkload
+import org.opendc.simulator.compute.workload.SimTraceWorkload
+import org.opendc.simulator.core.runBlockingSimulation
+
+/**
+ * A test suite for the [SimSpaceSharedHypervisor].
+ */
+@OptIn(ExperimentalCoroutinesApi::class)
+internal class SimSpaceSharedHypervisorTest {
+ private lateinit var machineModel: SimMachineModel
+
+ @BeforeEach
+ fun setUp() {
+ val cpuNode = ProcessingNode("Intel", "Xeon", "amd64", 1)
+ machineModel = SimMachineModel(
+ cpus = List(cpuNode.coreCount) { ProcessingUnit(cpuNode, it, 3200.0) },
+ memory = List(4) { MemoryUnit("Crucial", "MTA18ASF4G72AZ-3G2B1", 3200.0, 32_000) }
+ )
+ }
+
+ /**
+ * Test a trace workload.
+ */
+ @Test
+ fun testTrace() = runBlockingSimulation {
+ val usagePm = mutableListOf<Double>()
+ val usageVm = mutableListOf<Double>()
+
+ val duration = 5 * 60L
+ val workloadA =
+ SimTraceWorkload(
+ sequenceOf(
+ SimTraceWorkload.Fragment(duration * 1000, 28.0, 1),
+ SimTraceWorkload.Fragment(duration * 1000, 3500.0, 1),
+ SimTraceWorkload.Fragment(duration * 1000, 0.0, 1),
+ SimTraceWorkload.Fragment(duration * 1000, 183.0, 1)
+ ),
+ )
+
+ val machine = SimBareMetalMachine(
+ coroutineContext, clock, machineModel, PerformanceScalingGovernor(),
+ SimpleScalingDriver(ConstantPowerModel(0.0))
+ )
+ val hypervisor = SimSpaceSharedHypervisor()
+
+ val colA = launch { machine.usage.toList(usagePm) }
+ launch { machine.run(hypervisor) }
+
+ yield()
+
+ val vm = hypervisor.createMachine(machineModel)
+ val colB = launch { vm.usage.toList(usageVm) }
+ vm.run(workloadA)
+ yield()
+
+ vm.close()
+ machine.close()
+ colA.cancel()
+ colB.cancel()
+
+ assertAll(
+ { assertEquals(listOf(0.0, 0.00875, 1.0, 0.0, 0.0571875, 0.0), usagePm) { "Correct PM usage" } },
+ // Temporary limitation is that VMs do not emit usage information
+ // { assertEquals(listOf(0.0, 0.00875, 1.0, 0.0, 0.0571875, 0.0), usageVm) { "Correct VM usage" } },
+ { assertEquals(5 * 60L * 4000, clock.millis()) { "Took enough time" } }
+ )
+ }
+
+ /**
+ * Test runtime workload on hypervisor.
+ */
+ @Test
+ fun testRuntimeWorkload() = runBlockingSimulation {
+ val duration = 5 * 60L * 1000
+ val workload = SimRuntimeWorkload(duration)
+ val machine = SimBareMetalMachine(
+ coroutineContext, clock, machineModel, PerformanceScalingGovernor(),
+ SimpleScalingDriver(ConstantPowerModel(0.0))
+ )
+ val hypervisor = SimSpaceSharedHypervisor()
+
+ launch { machine.run(hypervisor) }
+ yield()
+ val vm = hypervisor.createMachine(machineModel)
+ vm.run(workload)
+ vm.close()
+ machine.close()
+
+ assertEquals(duration, clock.millis()) { "Took enough time" }
+ }
+
+ /**
+ * Test FLOPs workload on hypervisor.
+ */
+ @Test
+ fun testFlopsWorkload() = runBlockingSimulation {
+ val duration = 5 * 60L * 1000
+ val workload = SimFlopsWorkload((duration * 3.2).toLong(), 1.0)
+ val machine = SimBareMetalMachine(
+ coroutineContext, clock, machineModel, PerformanceScalingGovernor(),
+ SimpleScalingDriver(ConstantPowerModel(0.0))
+ )
+ val hypervisor = SimSpaceSharedHypervisor()
+
+ launch { machine.run(hypervisor) }
+ yield()
+ val vm = hypervisor.createMachine(machineModel)
+ vm.run(workload)
+ machine.close()
+
+ assertEquals(duration, clock.millis()) { "Took enough time" }
+ }
+
+ /**
+ * Test two workloads running sequentially.
+ */
+ @Test
+ fun testTwoWorkloads() = runBlockingSimulation {
+ val duration = 5 * 60L * 1000
+ val machine = SimBareMetalMachine(
+ coroutineContext, clock, machineModel, PerformanceScalingGovernor(),
+ SimpleScalingDriver(ConstantPowerModel(0.0))
+ )
+ val hypervisor = SimSpaceSharedHypervisor()
+
+ launch { machine.run(hypervisor) }
+ yield()
+
+ val vm = hypervisor.createMachine(machineModel)
+ vm.run(SimRuntimeWorkload(duration))
+ vm.close()
+
+ val vm2 = hypervisor.createMachine(machineModel)
+ vm2.run(SimRuntimeWorkload(duration))
+ vm2.close()
+ machine.close()
+
+ assertEquals(duration * 2, clock.millis()) { "Took enough time" }
+ }
+
+ /**
+ * Test concurrent workloads on the machine.
+ */
+ @Test
+ fun testConcurrentWorkloadFails() = runBlockingSimulation {
+ val machine = SimBareMetalMachine(
+ coroutineContext, clock, machineModel, PerformanceScalingGovernor(),
+ SimpleScalingDriver(ConstantPowerModel(0.0))
+ )
+ val hypervisor = SimSpaceSharedHypervisor()
+
+ launch { machine.run(hypervisor) }
+ yield()
+
+ hypervisor.createMachine(machineModel)
+
+ assertAll(
+ { assertFalse(hypervisor.canFit(machineModel)) },
+ { assertThrows<IllegalArgumentException> { hypervisor.createMachine(machineModel) } }
+ )
+
+ machine.close()
+ }
+
+ /**
+ * Test concurrent workloads on the machine.
+ */
+ @Test
+ fun testConcurrentWorkloadSucceeds() = runBlockingSimulation {
+ val machine = SimBareMetalMachine(
+ coroutineContext, clock, machineModel, PerformanceScalingGovernor(),
+ SimpleScalingDriver(ConstantPowerModel(0.0))
+ )
+ val hypervisor = SimSpaceSharedHypervisor()
+
+ launch { machine.run(hypervisor) }
+ yield()
+
+ hypervisor.createMachine(machineModel).close()
+
+ assertAll(
+ { assertTrue(hypervisor.canFit(machineModel)) },
+ { assertDoesNotThrow { hypervisor.createMachine(machineModel) } }
+ )
+
+ machine.close()
+ }
+}
diff --git a/opendc-simulator/opendc-simulator-compute/src/test/kotlin/org/opendc/simulator/compute/cpufreq/DemandScalingGovernorTest.kt b/opendc-simulator/opendc-simulator-compute/src/test/kotlin/org/opendc/simulator/compute/cpufreq/DemandScalingGovernorTest.kt
new file mode 100644
index 00000000..c482d348
--- /dev/null
+++ b/opendc-simulator/opendc-simulator-compute/src/test/kotlin/org/opendc/simulator/compute/cpufreq/DemandScalingGovernorTest.kt
@@ -0,0 +1,48 @@
+/*
+ * 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.cpufreq
+
+import io.mockk.every
+import io.mockk.mockk
+import io.mockk.verify
+import org.junit.jupiter.api.Test
+
+/**
+ * Test suite for the [DemandScalingGovernor]
+ */
+internal class DemandScalingGovernorTest {
+ @Test
+ fun testSetDemandLimit() {
+ val ctx = mockk<ScalingContext>(relaxUnitFun = true)
+
+ every { ctx.cpu.speed } returns 2100.0
+
+ val logic = DemandScalingGovernor().createLogic(ctx)
+
+ logic.onStart()
+ verify(exactly = 0) { ctx.setTarget(any()) }
+
+ logic.onLimit()
+ verify(exactly = 1) { ctx.setTarget(2100.0) }
+ }
+}
diff --git a/opendc-simulator/opendc-simulator-compute/src/test/kotlin/org/opendc/simulator/compute/cpufreq/PStateScalingDriverTest.kt b/opendc-simulator/opendc-simulator-compute/src/test/kotlin/org/opendc/simulator/compute/cpufreq/PStateScalingDriverTest.kt
new file mode 100644
index 00000000..bbea3ee2
--- /dev/null
+++ b/opendc-simulator/opendc-simulator-compute/src/test/kotlin/org/opendc/simulator/compute/cpufreq/PStateScalingDriverTest.kt
@@ -0,0 +1,132 @@
+/*
+ * 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.cpufreq
+
+import io.mockk.every
+import io.mockk.mockk
+import org.junit.jupiter.api.Assertions.assertEquals
+import org.junit.jupiter.api.Test
+import org.opendc.simulator.compute.SimBareMetalMachine
+import org.opendc.simulator.compute.SimProcessingUnit
+import org.opendc.simulator.compute.power.ConstantPowerModel
+import org.opendc.simulator.compute.power.LinearPowerModel
+
+/**
+ * Test suite for [PStateScalingDriver].
+ */
+internal class PStateScalingDriverTest {
+ @Test
+ fun testPowerWithoutGovernor() {
+ val machine = mockk<SimBareMetalMachine>()
+
+ val driver = PStateScalingDriver(
+ sortedMapOf(
+ 2800.0 to ConstantPowerModel(200.0),
+ 3300.0 to ConstantPowerModel(300.0),
+ 3600.0 to ConstantPowerModel(350.0),
+ )
+ )
+
+ val logic = driver.createLogic(machine)
+ assertEquals(200.0, logic.computePower())
+ }
+
+ @Test
+ fun testPowerWithSingleGovernor() {
+ val machine = mockk<SimBareMetalMachine>()
+ val cpu = mockk<SimProcessingUnit>()
+
+ every { cpu.model.frequency } returns 4100.0
+ every { cpu.speed } returns 1200.0
+
+ val driver = PStateScalingDriver(
+ sortedMapOf(
+ 2800.0 to ConstantPowerModel(200.0),
+ 3300.0 to ConstantPowerModel(300.0),
+ 3600.0 to ConstantPowerModel(350.0),
+ )
+ )
+
+ val logic = driver.createLogic(machine)
+
+ val scalingContext = logic.createContext(cpu)
+ scalingContext.setTarget(3200.0)
+
+ assertEquals(300.0, logic.computePower())
+ }
+
+ @Test
+ fun testPowerWithMultipleGovernors() {
+ val machine = mockk<SimBareMetalMachine>()
+ val cpu = mockk<SimProcessingUnit>()
+
+ every { cpu.model.frequency } returns 4100.0
+ every { cpu.speed } returns 1200.0
+
+ val driver = PStateScalingDriver(
+ sortedMapOf(
+ 2800.0 to ConstantPowerModel(200.0),
+ 3300.0 to ConstantPowerModel(300.0),
+ 3600.0 to ConstantPowerModel(350.0),
+ )
+ )
+
+ val logic = driver.createLogic(machine)
+
+ val scalingContextA = logic.createContext(cpu)
+ scalingContextA.setTarget(1000.0)
+
+ val scalingContextB = logic.createContext(cpu)
+ scalingContextB.setTarget(3400.0)
+
+ assertEquals(350.0, logic.computePower())
+ }
+
+ @Test
+ fun testPowerBasedOnUtilization() {
+ val machine = mockk<SimBareMetalMachine>()
+ val cpu = mockk<SimProcessingUnit>()
+
+ every { cpu.model.frequency } returns 4200.0
+
+ val driver = PStateScalingDriver(
+ sortedMapOf(
+ 2800.0 to LinearPowerModel(200.0, 100.0),
+ 3300.0 to LinearPowerModel(250.0, 150.0),
+ 4000.0 to LinearPowerModel(300.0, 200.0),
+ )
+ )
+
+ val logic = driver.createLogic(machine)
+
+ val scalingContext = logic.createContext(cpu)
+
+ every { cpu.speed } returns 1400.0
+ scalingContext.setTarget(1400.0)
+ assertEquals(150.0, logic.computePower())
+
+ every { cpu.speed } returns 1400.0
+ scalingContext.setTarget(4000.0)
+ assertEquals(235.0, logic.computePower())
+ }
+}
diff --git a/opendc-simulator/opendc-simulator-compute/src/test/kotlin/org/opendc/simulator/compute/power/PowerModelTest.kt b/opendc-simulator/opendc-simulator-compute/src/test/kotlin/org/opendc/simulator/compute/power/PowerModelTest.kt
new file mode 100644
index 00000000..dd93302b
--- /dev/null
+++ b/opendc-simulator/opendc-simulator-compute/src/test/kotlin/org/opendc/simulator/compute/power/PowerModelTest.kt
@@ -0,0 +1,70 @@
+package org.opendc.simulator.compute.power
+
+import org.junit.jupiter.api.Assertions.assertAll
+import org.junit.jupiter.api.Assertions.assertEquals
+import org.junit.jupiter.api.Test
+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
+
+internal class PowerModelTest {
+ private val epsilon = 10.0.pow(-3)
+ private val cpuUtil = 0.9
+
+ @ParameterizedTest
+ @MethodSource("MachinePowerModelArgs")
+ fun `compute power consumption given CPU loads`(
+ powerModel: PowerModel,
+ expectedPowerConsumption: Double
+ ) {
+ val computedPowerConsumption = powerModel.computePower(cpuUtil)
+ assertEquals(expectedPowerConsumption, computedPowerConsumption, epsilon)
+ }
+
+ @ParameterizedTest
+ @MethodSource("MachinePowerModelArgs")
+ fun `ignore idle power when computing power consumptions`(
+ powerModel: PowerModel,
+ expectedPowerConsumption: Double
+ ) {
+ val zeroPowerModel = ZeroIdlePowerDecorator(powerModel)
+
+ assertAll(
+ { assertEquals(expectedPowerConsumption, zeroPowerModel.computePower(cpuUtil), epsilon) },
+ { assertEquals(0.0, zeroPowerModel.computePower(0.0)) }
+ )
+ }
+
+ @Test
+ fun `compute power draw by the SPEC benchmark model`() {
+ val powerModel = InterpolationPowerModel("IBMx3550M3_XeonX5675")
+
+ assertAll(
+ { assertEquals(58.4, powerModel.computePower(0.0)) },
+ { assertEquals(58.4 + (98 - 58.4) / 5, powerModel.computePower(0.02)) },
+ { assertEquals(98.0, powerModel.computePower(0.1)) },
+ { assertEquals(140.0, powerModel.computePower(0.5)) },
+ { assertEquals(189.0, powerModel.computePower(0.8)) },
+ { assertEquals(189.0 + 0.7 * 10 * (205 - 189) / 10, powerModel.computePower(0.87)) },
+ { assertEquals(205.0, powerModel.computePower(0.9)) },
+ { assertEquals(222.0, powerModel.computePower(1.0)) },
+ )
+ }
+
+ @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.0), 335.0),
+ Arguments.of(SquarePowerModel(350.0, 200.0), 321.5),
+ Arguments.of(CubicPowerModel(350.0, 200.0), 309.35),
+ Arguments.of(SqrtPowerModel(350.0, 200.0), 342.302),
+ Arguments.of(MsePowerModel(350.0, 200.0, 1.4), 340.571),
+ Arguments.of(AsymptoticPowerModel(350.0, 200.0, 0.3, false), 338.765),
+ Arguments.of(AsymptoticPowerModel(350.0, 200.0, 0.3, true), 323.072),
+ )
+ }
+}
diff --git a/opendc-simulator/opendc-simulator-compute/src/test/kotlin/org/opendc/simulator/compute/workload/SimFlopsWorkloadTest.kt b/opendc-simulator/opendc-simulator-compute/src/test/kotlin/org/opendc/simulator/compute/workload/SimFlopsWorkloadTest.kt
new file mode 100644
index 00000000..b3e57453
--- /dev/null
+++ b/opendc-simulator/opendc-simulator-compute/src/test/kotlin/org/opendc/simulator/compute/workload/SimFlopsWorkloadTest.kt
@@ -0,0 +1,59 @@
+/*
+ * 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.simulator.compute.workload
+
+import org.junit.jupiter.api.Test
+import org.junit.jupiter.api.assertThrows
+
+/**
+ * Test suite for [SimFlopsWorkload] class.
+ */
+class SimFlopsWorkloadTest {
+ @Test
+ fun testFlopsNonNegative() {
+ assertThrows<IllegalArgumentException>("FLOPs must be non-negative") {
+ SimFlopsWorkload(-1)
+ }
+ }
+
+ @Test
+ fun testUtilizationNonZero() {
+ assertThrows<IllegalArgumentException>("Utilization cannot be zero") {
+ SimFlopsWorkload(1, 0.0)
+ }
+ }
+
+ @Test
+ fun testUtilizationPositive() {
+ assertThrows<IllegalArgumentException>("Utilization cannot be negative") {
+ SimFlopsWorkload(1, -1.0)
+ }
+ }
+
+ @Test
+ fun testUtilizationNotLargerThanOne() {
+ assertThrows<IllegalArgumentException>("Utilization cannot be larger than one") {
+ SimFlopsWorkload(1, 2.0)
+ }
+ }
+}
diff --git a/opendc-simulator/opendc-simulator-compute/src/test/resources/spec_machines.yml b/opendc-simulator/opendc-simulator-compute/src/test/resources/spec_machines.yml
new file mode 100644
index 00000000..d51cba80
--- /dev/null
+++ b/opendc-simulator/opendc-simulator-compute/src/test/resources/spec_machines.yml
@@ -0,0 +1,29 @@
+---
+# The power model of an IBM server x3550 (2 x [Xeon X5675 3067 MHz, 6 cores], 16GB).<br/>
+# <a href="http://www.spec.org/power_ssj2008/results/res2011q2/power_ssj2008-20110406-00368.html">
+# http://www.spec.org/power_ssj2008/results/res2011q2/power_ssj2008-20110406-00368.html</a>
+IBMx3550M3_XeonX5675: [58.4, 98.0, 109.0, 118.0, 128.0, 140.0, 153.0, 170.0, 189.0, 205.0, 222.0]
+ # The power model of an IBM server x3550 (2 x [Xeon X5670 2933 MHz, 6 cores], 12GB).<br/>
+ # <a href="http://www.spec.org/power_ssj2008/results/res2010q2/power_ssj2008-20100315-00239.html">
+ # http://www.spec.org/power_ssj2008/results/res2010q2/power_ssj2008-20100315-00239.html</a>
+IBMx3550M3_XeonX5670: [66.0, 107.0, 120.0, 131.0, 143.0, 156.0, 173.0, 191.0, 211.0, 229.0, 247.0]
+ # the power model of an IBM server x3250 (1 x [Xeon X3480 3067 MHz, 4 cores], 8GB).<br/>
+ # <a href="http://www.spec.org/power_ssj2008/results/res2010q4/power_ssj2008-20101001-00297.html">
+ # http://www.spec.org/power_ssj2008/results/res2010q4/power_ssj2008-20101001-00297.html</a>
+IBMx3250M3_XeonX3480: [42.3, 46.7, 49.7, 55.4, 61.8, 69.3, 76.1, 87.0, 96.1, 106.0, 113.0]
+ # The power model of an IBM server x3250 (1 x [Xeon X3470 2933 MHz, 4 cores], 8GB).<br/>
+ # <a href="http://www.spec.org/power_ssj2008/results/res2009q4/power_ssj2008-20091104-00213.html">
+ # http://www.spec.org/power_ssj2008/results/res2009q4/power_ssj2008-20091104-00213.html</a>
+IBMx3250M3_XeonX3470: [41.6, 46.7, 52.3, 57.9, 65.4, 73.0, 80.7, 89.5, 99.6, 105.0, 113.0]
+ # The power model of an HP ProLiant ML110 G5 (1 x [Xeon 3075 2660 MHz, 2 cores], 4GB).<br/>
+ # <a href="http://www.spec.org/power_ssj2008/results/res2011q1/power_ssj2008-20110124-00339.html">
+ # http://www.spec.org/power_ssj2008/results/res2011q1/power_ssj2008-20110124-00339.html</a>
+HPProLiantMl110G5_Xeon3075: [93.7, 97.0, 101.0, 105.0, 110.0, 116.0, 121.0, 125.0, 129.0, 133.0, 135.0]
+ # The power model of an HP ProLiant ML110 G4 (1 x [Xeon 3040 1860 MHz, 2 cores], 4GB).<br/>
+ # <a href="http://www.spec.org/power_ssj2008/results/res2011q1/power_ssj2008-20110127-00342.html">
+ # http://www.spec.org/power_ssj2008/results/res2011q1/power_ssj2008-20110127-00342.html</a>
+HPProLiantMl110G4_Xeon3040: [86.0, 89.4, 92.6, 96.0, 99.5, 102.0, 106.0, 108.0, 112.0, 114.0, 117.0]
+ # The power model of an HP ProLiant ML110 G3 (1 x [Pentium D930 3000 MHz, 2 cores], 4GB).<br/>
+ # <a href="http://www.spec.org/power_ssj2008/results/res2011q1/power_ssj2008-20110127-00342.html">
+ # http://www.spec.org/power_ssj2008/results/res2011q1/power_ssj2008-20110127-00342.html</a>
+HPProLiantMl110G3_PentiumD930: [105.0, 112.0, 118.0, 125.0, 131.0, 137.0, 147.0, 153.0, 157.0, 164.0, 169.0]