summaryrefslogtreecommitdiff
path: root/opendc-simulator/opendc-simulator-compute
diff options
context:
space:
mode:
Diffstat (limited to 'opendc-simulator/opendc-simulator-compute')
-rw-r--r--opendc-simulator/opendc-simulator-compute/build.gradle.kts38
-rw-r--r--opendc-simulator/opendc-simulator-compute/src/jmh/kotlin/org/opendc/simulator/compute/BenchmarkHelpers.kt43
-rw-r--r--opendc-simulator/opendc-simulator-compute/src/jmh/kotlin/org/opendc/simulator/compute/SimMachineBenchmarks.kt158
-rw-r--r--opendc-simulator/opendc-simulator-compute/src/main/kotlin/org/opendc/simulator/compute/SimAbstractHypervisor.kt189
-rw-r--r--opendc-simulator/opendc-simulator-compute/src/main/kotlin/org/opendc/simulator/compute/SimAbstractMachine.kt124
-rw-r--r--opendc-simulator/opendc-simulator-compute/src/main/kotlin/org/opendc/simulator/compute/SimBareMetalMachine.kt134
-rw-r--r--opendc-simulator/opendc-simulator-compute/src/main/kotlin/org/opendc/simulator/compute/SimFairShareHypervisor.kt56
-rw-r--r--opendc-simulator/opendc-simulator-compute/src/main/kotlin/org/opendc/simulator/compute/SimFairShareHypervisorProvider.kt32
-rw-r--r--opendc-simulator/opendc-simulator-compute/src/main/kotlin/org/opendc/simulator/compute/SimHypervisor.kt71
-rw-r--r--opendc-simulator/opendc-simulator-compute/src/main/kotlin/org/opendc/simulator/compute/SimHypervisorProvider.kt41
-rw-r--r--opendc-simulator/opendc-simulator-compute/src/main/kotlin/org/opendc/simulator/compute/SimMachine.kt51
-rw-r--r--opendc-simulator/opendc-simulator-compute/src/main/kotlin/org/opendc/simulator/compute/SimMachineContext.kt53
-rw-r--r--opendc-simulator/opendc-simulator-compute/src/main/kotlin/org/opendc/simulator/compute/SimMachineModel.kt34
-rw-r--r--opendc-simulator/opendc-simulator-compute/src/main/kotlin/org/opendc/simulator/compute/SimProcessingUnit.kt41
-rw-r--r--opendc-simulator/opendc-simulator-compute/src/main/kotlin/org/opendc/simulator/compute/SimSpaceSharedHypervisor.kt38
-rw-r--r--opendc-simulator/opendc-simulator-compute/src/main/kotlin/org/opendc/simulator/compute/SimSpaceSharedHypervisorProvider.kt32
-rw-r--r--opendc-simulator/opendc-simulator-compute/src/main/kotlin/org/opendc/simulator/compute/cpufreq/DemandScalingGovernor.kt36
-rw-r--r--opendc-simulator/opendc-simulator-compute/src/main/kotlin/org/opendc/simulator/compute/cpufreq/PStateScalingDriver.kt86
-rw-r--r--opendc-simulator/opendc-simulator-compute/src/main/kotlin/org/opendc/simulator/compute/cpufreq/PerformanceScalingGovernor.kt36
-rw-r--r--opendc-simulator/opendc-simulator-compute/src/main/kotlin/org/opendc/simulator/compute/cpufreq/ScalingContext.kt46
-rw-r--r--opendc-simulator/opendc-simulator-compute/src/main/kotlin/org/opendc/simulator/compute/cpufreq/ScalingDriver.kt53
-rw-r--r--opendc-simulator/opendc-simulator-compute/src/main/kotlin/org/opendc/simulator/compute/cpufreq/ScalingGovernor.kt54
-rw-r--r--opendc-simulator/opendc-simulator-compute/src/main/kotlin/org/opendc/simulator/compute/cpufreq/SimpleScalingDriver.kt49
-rw-r--r--opendc-simulator/opendc-simulator-compute/src/main/kotlin/org/opendc/simulator/compute/interference/PerformanceInterferenceModel.kt134
-rw-r--r--opendc-simulator/opendc-simulator-compute/src/main/kotlin/org/opendc/simulator/compute/model/MemoryUnit.kt38
-rw-r--r--opendc-simulator/opendc-simulator-compute/src/main/kotlin/org/opendc/simulator/compute/model/ProcessingNode.kt38
-rw-r--r--opendc-simulator/opendc-simulator-compute/src/main/kotlin/org/opendc/simulator/compute/model/ProcessingUnit.kt36
-rw-r--r--opendc-simulator/opendc-simulator-compute/src/main/kotlin/org/opendc/simulator/compute/power/AsymptoticPowerModel.kt31
-rw-r--r--opendc-simulator/opendc-simulator-compute/src/main/kotlin/org/opendc/simulator/compute/power/ConstantPowerModel.kt10
-rw-r--r--opendc-simulator/opendc-simulator-compute/src/main/kotlin/org/opendc/simulator/compute/power/CubicPowerModel.kt19
-rw-r--r--opendc-simulator/opendc-simulator-compute/src/main/kotlin/org/opendc/simulator/compute/power/InterpolationPowerModel.kt54
-rw-r--r--opendc-simulator/opendc-simulator-compute/src/main/kotlin/org/opendc/simulator/compute/power/LinearPowerModel.kt20
-rw-r--r--opendc-simulator/opendc-simulator-compute/src/main/kotlin/org/opendc/simulator/compute/power/MsePowerModel.kt27
-rw-r--r--opendc-simulator/opendc-simulator-compute/src/main/kotlin/org/opendc/simulator/compute/power/PowerModel.kt16
-rw-r--r--opendc-simulator/opendc-simulator-compute/src/main/kotlin/org/opendc/simulator/compute/power/SqrtPowerModel.kt19
-rw-r--r--opendc-simulator/opendc-simulator-compute/src/main/kotlin/org/opendc/simulator/compute/power/SquarePowerModel.kt19
-rw-r--r--opendc-simulator/opendc-simulator-compute/src/main/kotlin/org/opendc/simulator/compute/power/ZeroIdlePowerDecorator.kt17
-rw-r--r--opendc-simulator/opendc-simulator-compute/src/main/kotlin/org/opendc/simulator/compute/workload/SimFlopsWorkload.kt53
-rw-r--r--opendc-simulator/opendc-simulator-compute/src/main/kotlin/org/opendc/simulator/compute/workload/SimRuntimeWorkload.kt53
-rw-r--r--opendc-simulator/opendc-simulator-compute/src/main/kotlin/org/opendc/simulator/compute/workload/SimTraceWorkload.kt94
-rw-r--r--opendc-simulator/opendc-simulator-compute/src/main/kotlin/org/opendc/simulator/compute/workload/SimWorkload.kt45
-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
49 files changed, 3097 insertions, 0 deletions
diff --git a/opendc-simulator/opendc-simulator-compute/build.gradle.kts b/opendc-simulator/opendc-simulator-compute/build.gradle.kts
new file mode 100644
index 00000000..4b0069e3
--- /dev/null
+++ b/opendc-simulator/opendc-simulator-compute/build.gradle.kts
@@ -0,0 +1,38 @@
+/*
+ * 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.
+ */
+
+description = "Library for simulating computing workloads"
+
+plugins {
+ `kotlin-library-conventions`
+ `testing-conventions`
+ `jacoco-conventions`
+ `benchmark-conventions`
+}
+
+dependencies {
+ api(platform(project(":opendc-platform")))
+ api(project(":opendc-simulator:opendc-simulator-core"))
+ api(project(":opendc-simulator:opendc-simulator-resources"))
+ implementation(project(":opendc-utils"))
+ implementation("org.yaml:snakeyaml:1.28")
+}
diff --git a/opendc-simulator/opendc-simulator-compute/src/jmh/kotlin/org/opendc/simulator/compute/BenchmarkHelpers.kt b/opendc-simulator/opendc-simulator-compute/src/jmh/kotlin/org/opendc/simulator/compute/BenchmarkHelpers.kt
new file mode 100644
index 00000000..43bbfd0b
--- /dev/null
+++ b/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/opendc-simulator/opendc-simulator-compute/src/jmh/kotlin/org/opendc/simulator/compute/SimMachineBenchmarks.kt b/opendc-simulator/opendc-simulator-compute/src/jmh/kotlin/org/opendc/simulator/compute/SimMachineBenchmarks.kt
new file mode 100644
index 00000000..7b97a665
--- /dev/null
+++ b/opendc-simulator/opendc-simulator-compute/src/jmh/kotlin/org/opendc/simulator/compute/SimMachineBenchmarks.kt
@@ -0,0 +1,158 @@
+/*
+ * 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 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.SimWorkload
+import org.opendc.simulator.core.SimulationCoroutineScope
+import org.opendc.simulator.core.runBlockingSimulation
+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: SimulationCoroutineScope
+ private lateinit var clock: Clock
+ private lateinit var scheduler: TimerScheduler<Any>
+ private lateinit var machineModel: SimMachineModel
+
+ @Setup
+ fun setUp() {
+ scope = SimulationCoroutineScope()
+ 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<SimWorkload>
+
+ @Setup
+ fun setUp() {
+ workloads = Array(2) { createSimpleConsumer() }
+ }
+ }
+
+ @Benchmark
+ fun benchmarkBareMetal(state: Workload) {
+ return scope.runBlockingSimulation {
+ val machine = SimBareMetalMachine(
+ coroutineContext, clock, machineModel, PerformanceScalingGovernor(),
+ SimpleScalingDriver(ConstantPowerModel(0.0))
+ )
+ return@runBlockingSimulation machine.run(state.workloads[0])
+ }
+ }
+
+ @Benchmark
+ fun benchmarkSpaceSharedHypervisor(state: Workload) {
+ return scope.runBlockingSimulation {
+ val machine = SimBareMetalMachine(
+ coroutineContext, clock, machineModel, PerformanceScalingGovernor(),
+ SimpleScalingDriver(ConstantPowerModel(0.0))
+ )
+ val hypervisor = SimSpaceSharedHypervisor()
+
+ launch { machine.run(hypervisor) }
+
+ val vm = hypervisor.createMachine(machineModel)
+
+ try {
+ return@runBlockingSimulation vm.run(state.workloads[0])
+ } finally {
+ vm.close()
+ machine.close()
+ }
+ }
+ }
+
+ @Benchmark
+ fun benchmarkFairShareHypervisorSingle(state: Workload) {
+ return scope.runBlockingSimulation {
+ val machine = SimBareMetalMachine(
+ coroutineContext, clock, machineModel, PerformanceScalingGovernor(),
+ SimpleScalingDriver(ConstantPowerModel(0.0))
+ )
+ val hypervisor = SimFairShareHypervisor()
+
+ launch { machine.run(hypervisor) }
+
+ val vm = hypervisor.createMachine(machineModel)
+
+ try {
+ return@runBlockingSimulation vm.run(state.workloads[0])
+ } finally {
+ vm.close()
+ machine.close()
+ }
+ }
+ }
+
+ @Benchmark
+ fun benchmarkFairShareHypervisorDouble(state: Workload) {
+ return scope.runBlockingSimulation {
+ val machine = SimBareMetalMachine(
+ coroutineContext, clock, machineModel, PerformanceScalingGovernor(),
+ SimpleScalingDriver(ConstantPowerModel(0.0))
+ )
+ 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()
+ }
+ }
+}
diff --git a/opendc-simulator/opendc-simulator-compute/src/main/kotlin/org/opendc/simulator/compute/SimAbstractHypervisor.kt b/opendc-simulator/opendc-simulator-compute/src/main/kotlin/org/opendc/simulator/compute/SimAbstractHypervisor.kt
new file mode 100644
index 00000000..713376e7
--- /dev/null
+++ b/opendc-simulator/opendc-simulator-compute/src/main/kotlin/org/opendc/simulator/compute/SimAbstractHypervisor.kt
@@ -0,0 +1,189 @@
+/*
+ * 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.coroutineScope
+import kotlinx.coroutines.flow.MutableStateFlow
+import kotlinx.coroutines.flow.StateFlow
+import kotlinx.coroutines.launch
+import org.opendc.simulator.compute.interference.PerformanceInterferenceModel
+import org.opendc.simulator.compute.model.MemoryUnit
+import org.opendc.simulator.compute.model.ProcessingUnit
+import org.opendc.simulator.compute.workload.SimWorkload
+import org.opendc.simulator.resources.*
+import java.time.Clock
+
+/**
+ * Abstract implementation of the [SimHypervisor] interface.
+ */
+public abstract class SimAbstractHypervisor : SimHypervisor {
+ /**
+ * The machine on which the hypervisor runs.
+ */
+ private lateinit var context: SimMachineContext
+
+ /**
+ * The resource switch to use.
+ */
+ private lateinit var switch: SimResourceSwitch
+
+ /**
+ * The virtual machines running on this hypervisor.
+ */
+ private val _vms = mutableSetOf<VirtualMachine>()
+ override val vms: Set<SimMachine>
+ get() = _vms
+
+ /**
+ * Construct the [SimResourceSwitch] implementation that performs the actual scheduling of the CPUs.
+ */
+ public abstract fun createSwitch(ctx: SimMachineContext): SimResourceSwitch
+
+ /**
+ * Check whether the specified machine model fits on this hypervisor.
+ */
+ public abstract fun canFit(model: SimMachineModel, switch: SimResourceSwitch): Boolean
+
+ override fun canFit(model: SimMachineModel): Boolean {
+ return canFit(model, switch)
+ }
+
+ override fun createMachine(
+ model: SimMachineModel,
+ performanceInterferenceModel: PerformanceInterferenceModel?
+ ): SimMachine {
+ require(canFit(model)) { "Machine does not fit" }
+ val vm = VirtualMachine(model, performanceInterferenceModel)
+ _vms.add(vm)
+ return vm
+ }
+
+ /**
+ * A virtual machine running on the hypervisor.
+ *
+ * @property model The machine model of the virtual machine.
+ * @property performanceInterferenceModel The performance interference model to utilize.
+ */
+ private inner class VirtualMachine(
+ override val model: SimMachineModel,
+ val performanceInterferenceModel: PerformanceInterferenceModel? = null,
+ ) : SimMachine {
+ /**
+ * A [StateFlow] representing the CPU usage of the simulated machine.
+ */
+ override val usage: MutableStateFlow<Double> = MutableStateFlow(0.0)
+
+ /**
+ * A flag to indicate that the machine is terminated.
+ */
+ private var isTerminated = false
+
+ /**
+ * The vCPUs of the machine.
+ */
+ private val cpus = model.cpus.map { ProcessingUnitImpl(it, switch) }
+
+ /**
+ * Run the specified [SimWorkload] on this machine and suspend execution util the workload has finished.
+ */
+ override suspend fun run(workload: SimWorkload, meta: Map<String, Any>) {
+ coroutineScope {
+ require(!isTerminated) { "Machine is terminated" }
+
+ val ctx = object : SimMachineContext {
+ override val cpus: List<SimProcessingUnit> = this@VirtualMachine.cpus
+
+ override val memory: List<MemoryUnit>
+ get() = model.memory
+
+ override val clock: Clock
+ get() = this@SimAbstractHypervisor.context.clock
+
+ override val meta: Map<String, Any> = meta
+ }
+
+ workload.onStart(ctx)
+
+ for (cpu in cpus) {
+ launch {
+ cpu.consume(workload.getConsumer(ctx, cpu.model))
+ }
+ }
+ }
+ }
+
+ /**
+ * Terminate this VM instance.
+ */
+ override fun close() {
+ if (!isTerminated) {
+ isTerminated = true
+
+ cpus.forEach(SimProcessingUnit::close)
+ _vms.remove(this)
+ }
+ }
+ }
+
+ override fun onStart(ctx: SimMachineContext) {
+ context = ctx
+ switch = createSwitch(ctx)
+ }
+
+ override fun getConsumer(ctx: SimMachineContext, cpu: ProcessingUnit): SimResourceConsumer {
+ val forwarder = SimResourceForwarder()
+ switch.addInput(forwarder)
+ return forwarder
+ }
+
+ /**
+ * The [SimProcessingUnit] of this machine.
+ */
+ public inner class ProcessingUnitImpl(override val model: ProcessingUnit, switch: SimResourceSwitch) : SimProcessingUnit {
+ /**
+ * The actual resource supporting the processing unit.
+ */
+ private val source = switch.addOutput(model.frequency)
+
+ override val speed: Double = 0.0 /* TODO Implement */
+
+ override val state: SimResourceState
+ get() = source.state
+
+ override fun startConsumer(consumer: SimResourceConsumer) {
+ source.startConsumer(consumer)
+ }
+
+ override fun interrupt() {
+ source.interrupt()
+ }
+
+ override fun cancel() {
+ source.cancel()
+ }
+
+ override fun close() {
+ source.close()
+ }
+ }
+}
diff --git a/opendc-simulator/opendc-simulator-compute/src/main/kotlin/org/opendc/simulator/compute/SimAbstractMachine.kt b/opendc-simulator/opendc-simulator-compute/src/main/kotlin/org/opendc/simulator/compute/SimAbstractMachine.kt
new file mode 100644
index 00000000..0244c5c1
--- /dev/null
+++ b/opendc-simulator/opendc-simulator-compute/src/main/kotlin/org/opendc/simulator/compute/SimAbstractMachine.kt
@@ -0,0 +1,124 @@
+/*
+ * 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.*
+import kotlinx.coroutines.flow.MutableStateFlow
+import kotlinx.coroutines.flow.StateFlow
+import org.opendc.simulator.compute.model.MemoryUnit
+import org.opendc.simulator.compute.workload.SimWorkload
+import org.opendc.simulator.resources.consume
+import org.opendc.simulator.resources.consumer.SimSpeedConsumerAdapter
+import java.time.Clock
+import kotlin.coroutines.CoroutineContext
+
+/**
+ * Abstract implementation of the [SimMachine] interface.
+ */
+public abstract class SimAbstractMachine(private val clock: Clock) : SimMachine {
+ private val _usage = MutableStateFlow(0.0)
+ override val usage: StateFlow<Double>
+ get() = _usage
+
+ /**
+ * The speed of the CPU cores.
+ */
+ public val speed: DoubleArray
+ get() = _speed
+ private var _speed = doubleArrayOf()
+
+ /**
+ * A flag to indicate that the machine is terminated.
+ */
+ private var isTerminated = false
+
+ /**
+ * The [CoroutineContext] to run in.
+ */
+ protected abstract val context: CoroutineContext
+
+ /**
+ * The resources allocated for this machine.
+ */
+ protected abstract val cpus: List<SimProcessingUnit>
+
+ /**
+ * The execution context in which the workload runs.
+ */
+ private inner class Context(override val meta: Map<String, Any>) : SimMachineContext {
+ override val clock: Clock
+ get() = this@SimAbstractMachine.clock
+
+ override val cpus: List<SimProcessingUnit> = this@SimAbstractMachine.cpus
+
+ override val memory: List<MemoryUnit> = model.memory
+ }
+
+ /**
+ * Run the specified [SimWorkload] on this machine and suspend execution util the workload has finished.
+ */
+ override suspend fun run(workload: SimWorkload, meta: Map<String, Any>): Unit = withContext(context) {
+ require(!isTerminated) { "Machine is terminated" }
+ val ctx = Context(meta)
+ val totalCapacity = model.cpus.sumByDouble { it.frequency }
+
+ _speed = DoubleArray(model.cpus.size) { 0.0 }
+ var totalSpeed = 0.0
+
+ workload.onStart(ctx)
+
+ for (cpu in cpus) {
+ val model = cpu.model
+ val consumer = workload.getConsumer(ctx, model)
+ val adapter = SimSpeedConsumerAdapter(consumer) { newSpeed ->
+ val _speed = _speed
+ val _usage = _usage
+
+ val oldSpeed = _speed[model.id]
+ _speed[model.id] = newSpeed
+ totalSpeed = totalSpeed - oldSpeed + newSpeed
+
+ val newUsage = totalSpeed / totalCapacity
+ if (_usage.value != newUsage) {
+ updateUsage(totalSpeed / totalCapacity)
+ }
+ }
+
+ launch { cpu.consume(adapter) }
+ }
+ }
+
+ /**
+ * This method is invoked when the usage of the machine is updated.
+ */
+ protected open fun updateUsage(usage: Double) {
+ _usage.value = usage
+ }
+
+ override fun close() {
+ if (!isTerminated) {
+ isTerminated = true
+ cpus.forEach(SimProcessingUnit::close)
+ }
+ }
+}
diff --git a/opendc-simulator/opendc-simulator-compute/src/main/kotlin/org/opendc/simulator/compute/SimBareMetalMachine.kt b/opendc-simulator/opendc-simulator-compute/src/main/kotlin/org/opendc/simulator/compute/SimBareMetalMachine.kt
new file mode 100644
index 00000000..09ee601e
--- /dev/null
+++ b/opendc-simulator/opendc-simulator-compute/src/main/kotlin/org/opendc/simulator/compute/SimBareMetalMachine.kt
@@ -0,0 +1,134 @@
+/*
+ * 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.*
+import org.opendc.simulator.compute.cpufreq.ScalingDriver
+import org.opendc.simulator.compute.cpufreq.ScalingGovernor
+import org.opendc.simulator.compute.model.ProcessingUnit
+import org.opendc.simulator.resources.*
+import org.opendc.utils.TimerScheduler
+import java.time.Clock
+import kotlin.coroutines.*
+
+/**
+ * A simulated bare-metal machine that is able to run a single workload.
+ *
+ * A [SimBareMetalMachine] is a stateful object and you should be careful when operating this object concurrently. For
+ * example. the class expects only a single concurrent call to [run].
+ *
+ * @param context The [CoroutineContext] to run the simulated workload in.
+ * @param clock The virtual clock to track the simulation time.
+ * @param model The machine model to simulate.
+ */
+@OptIn(ExperimentalCoroutinesApi::class, InternalCoroutinesApi::class)
+public class SimBareMetalMachine(
+ context: CoroutineContext,
+ private val clock: Clock,
+ override val model: SimMachineModel,
+ scalingGovernor: ScalingGovernor,
+ scalingDriver: ScalingDriver
+) : SimAbstractMachine(clock) {
+ /**
+ * The [Job] associated with this machine.
+ */
+ private val scope = CoroutineScope(context + Job())
+
+ override val context: CoroutineContext = scope.coroutineContext
+
+ /**
+ * The [TimerScheduler] to use for scheduling the interrupts.
+ */
+ private val scheduler = TimerScheduler<Any>(this.context, clock)
+
+ override val cpus: List<SimProcessingUnit> = model.cpus.map { ProcessingUnitImpl(it) }
+
+ /**
+ * Construct the [ScalingDriver.Logic] for this machine.
+ */
+ private val scalingDriver = scalingDriver.createLogic(this)
+
+ /**
+ * The scaling contexts associated with each CPU.
+ */
+ private val scalingGovernors = cpus.map { cpu ->
+ scalingGovernor.createLogic(this.scalingDriver.createContext(cpu))
+ }
+
+ init {
+ scalingGovernors.forEach { it.onStart() }
+ }
+
+ /**
+ * The power draw of the machine.
+ */
+ public var powerDraw: Double = 0.0
+ private set
+
+ override fun updateUsage(usage: Double) {
+ super.updateUsage(usage)
+
+ scalingGovernors.forEach { it.onLimit() }
+ powerDraw = scalingDriver.computePower()
+ }
+
+ override fun close() {
+ super.close()
+
+ scheduler.close()
+ scope.cancel()
+ }
+
+ /**
+ * The [SimProcessingUnit] of this machine.
+ */
+ public inner class ProcessingUnitImpl(override val model: ProcessingUnit) : SimProcessingUnit {
+ /**
+ * The actual resource supporting the processing unit.
+ */
+ private val source = SimResourceSource(model.frequency, clock, scheduler)
+
+ override val speed: Double
+ get() = source.speed
+
+ override val state: SimResourceState
+ get() = source.state
+
+ override fun startConsumer(consumer: SimResourceConsumer) {
+ source.startConsumer(consumer)
+ }
+
+ override fun interrupt() {
+ source.interrupt()
+ }
+
+ override fun cancel() {
+ source.cancel()
+ }
+
+ override fun close() {
+ source.interrupt()
+ }
+ }
+}
diff --git a/opendc-simulator/opendc-simulator-compute/src/main/kotlin/org/opendc/simulator/compute/SimFairShareHypervisor.kt b/opendc-simulator/opendc-simulator-compute/src/main/kotlin/org/opendc/simulator/compute/SimFairShareHypervisor.kt
new file mode 100644
index 00000000..fa677de9
--- /dev/null
+++ b/opendc-simulator/opendc-simulator-compute/src/main/kotlin/org/opendc/simulator/compute/SimFairShareHypervisor.kt
@@ -0,0 +1,56 @@
+/*
+ * 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.SimWorkload
+import org.opendc.simulator.resources.*
+
+/**
+ * A [SimHypervisor] that distributes the computing requirements of multiple [SimWorkload] on a single
+ * [SimBareMetalMachine] concurrently using weighted fair sharing.
+ *
+ * @param listener The hypervisor listener to use.
+ */
+public class SimFairShareHypervisor(private val listener: SimHypervisor.Listener? = null) : SimAbstractHypervisor() {
+
+ override fun canFit(model: SimMachineModel, switch: SimResourceSwitch): Boolean = true
+
+ override fun createSwitch(ctx: SimMachineContext): SimResourceSwitch {
+ return SimResourceSwitchMaxMin(
+ ctx.clock,
+ object : SimResourceSwitchMaxMin.Listener {
+ override fun onSliceFinish(
+ switch: SimResourceSwitchMaxMin,
+ requestedWork: Long,
+ grantedWork: Long,
+ overcommittedWork: Long,
+ interferedWork: Long,
+ cpuUsage: Double,
+ cpuDemand: Double
+ ) {
+ listener?.onSliceFinish(this@SimFairShareHypervisor, requestedWork, grantedWork, overcommittedWork, interferedWork, cpuUsage, cpuDemand)
+ }
+ }
+ )
+ }
+}
diff --git a/opendc-simulator/opendc-simulator-compute/src/main/kotlin/org/opendc/simulator/compute/SimFairShareHypervisorProvider.kt b/opendc-simulator/opendc-simulator-compute/src/main/kotlin/org/opendc/simulator/compute/SimFairShareHypervisorProvider.kt
new file mode 100644
index 00000000..02eb6ad0
--- /dev/null
+++ b/opendc-simulator/opendc-simulator-compute/src/main/kotlin/org/opendc/simulator/compute/SimFairShareHypervisorProvider.kt
@@ -0,0 +1,32 @@
+/*
+ * 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
+
+/**
+ * A [SimHypervisorProvider] for the [SimFairShareHypervisor] implementation.
+ */
+public class SimFairShareHypervisorProvider : SimHypervisorProvider {
+ override val id: String = "fair-share"
+
+ override fun create(listener: SimHypervisor.Listener?): SimHypervisor = SimFairShareHypervisor(listener)
+}
diff --git a/opendc-simulator/opendc-simulator-compute/src/main/kotlin/org/opendc/simulator/compute/SimHypervisor.kt b/opendc-simulator/opendc-simulator-compute/src/main/kotlin/org/opendc/simulator/compute/SimHypervisor.kt
new file mode 100644
index 00000000..4a233fec
--- /dev/null
+++ b/opendc-simulator/opendc-simulator-compute/src/main/kotlin/org/opendc/simulator/compute/SimHypervisor.kt
@@ -0,0 +1,71 @@
+/*
+ * 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 org.opendc.simulator.compute.interference.PerformanceInterferenceModel
+import org.opendc.simulator.compute.workload.SimWorkload
+
+/**
+ * A SimHypervisor facilitates the execution of multiple concurrent [SimWorkload]s, while acting as a single workload
+ * to a [SimBareMetalMachine].
+ */
+public interface SimHypervisor : SimWorkload {
+ /**
+ * The machines running on the hypervisor.
+ */
+ public val vms: Set<SimMachine>
+
+ /**
+ * Determine whether the specified machine characterized by [model] can fit on this hypervisor at this moment.
+ */
+ public fun canFit(model: SimMachineModel): Boolean
+
+ /**
+ * Create a [SimMachine] instance on which users may run a [SimWorkload].
+ *
+ * @param model The machine to create.
+ * @param performanceInterferenceModel The performance interference model to use.
+ */
+ public fun createMachine(
+ model: SimMachineModel,
+ performanceInterferenceModel: PerformanceInterferenceModel? = null
+ ): SimMachine
+
+ /**
+ * Event listener for hypervisor events.
+ */
+ public interface Listener {
+ /**
+ * This method is invoked when a slice is finished.
+ */
+ public fun onSliceFinish(
+ hypervisor: SimHypervisor,
+ requestedWork: Long,
+ grantedWork: Long,
+ overcommittedWork: Long,
+ interferedWork: Long,
+ cpuUsage: Double,
+ cpuDemand: Double
+ )
+ }
+}
diff --git a/opendc-simulator/opendc-simulator-compute/src/main/kotlin/org/opendc/simulator/compute/SimHypervisorProvider.kt b/opendc-simulator/opendc-simulator-compute/src/main/kotlin/org/opendc/simulator/compute/SimHypervisorProvider.kt
new file mode 100644
index 00000000..a5b4526b
--- /dev/null
+++ b/opendc-simulator/opendc-simulator-compute/src/main/kotlin/org/opendc/simulator/compute/SimHypervisorProvider.kt
@@ -0,0 +1,41 @@
+/*
+ * 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
+
+/**
+ * A service provider interface for constructing a [SimHypervisor].
+ */
+public interface SimHypervisorProvider {
+ /**
+ * A unique identifier for this hypervisor implementation.
+ *
+ * Each hypervisor must provide a unique ID, so that they can be selected by the user.
+ * When in doubt, you may use the fully qualified name of your custom [SimHypervisor] implementation class.
+ */
+ public val id: String
+
+ /**
+ * Create a [SimHypervisor] instance with the specified [listener].
+ */
+ public fun create(listener: SimHypervisor.Listener? = null): SimHypervisor
+}
diff --git a/opendc-simulator/opendc-simulator-compute/src/main/kotlin/org/opendc/simulator/compute/SimMachine.kt b/opendc-simulator/opendc-simulator-compute/src/main/kotlin/org/opendc/simulator/compute/SimMachine.kt
new file mode 100644
index 00000000..bfaa60bc
--- /dev/null
+++ b/opendc-simulator/opendc-simulator-compute/src/main/kotlin/org/opendc/simulator/compute/SimMachine.kt
@@ -0,0 +1,51 @@
+/*
+ * 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.flow.StateFlow
+import org.opendc.simulator.compute.workload.SimWorkload
+
+/**
+ * A generic machine that is able to run a [SimWorkload].
+ */
+public interface SimMachine : AutoCloseable {
+ /**
+ * The model of the machine containing its specifications.
+ */
+ public val model: SimMachineModel
+
+ /**
+ * A [StateFlow] representing the CPU usage of the simulated machine.
+ */
+ public val usage: StateFlow<Double>
+
+ /**
+ * Run the specified [SimWorkload] on this machine and suspend execution util the workload has finished.
+ */
+ public suspend fun run(workload: SimWorkload, meta: Map<String, Any> = emptyMap())
+
+ /**
+ * Terminate this machine.
+ */
+ public override fun close()
+}
diff --git a/opendc-simulator/opendc-simulator-compute/src/main/kotlin/org/opendc/simulator/compute/SimMachineContext.kt b/opendc-simulator/opendc-simulator-compute/src/main/kotlin/org/opendc/simulator/compute/SimMachineContext.kt
new file mode 100644
index 00000000..c2523a2a
--- /dev/null
+++ b/opendc-simulator/opendc-simulator-compute/src/main/kotlin/org/opendc/simulator/compute/SimMachineContext.kt
@@ -0,0 +1,53 @@
+/*
+ * 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 org.opendc.simulator.compute.model.MemoryUnit
+import java.time.Clock
+
+/**
+ * A simulated execution context in which a bootable image runs. This interface represents the
+ * firmware interface between the running image (e.g. operating system) and the physical or virtual firmware on
+ * which the image runs.
+ */
+public interface SimMachineContext {
+ /**
+ * The virtual clock tracking simulation time.
+ */
+ public val clock: Clock
+
+ /**
+ * The metadata associated with the context.
+ */
+ public val meta: Map<String, Any>
+
+ /**
+ * The CPUs available on the machine.
+ */
+ public val cpus: List<SimProcessingUnit>
+
+ /**
+ * The memory available on the machine
+ */
+ public val memory: List<MemoryUnit>
+}
diff --git a/opendc-simulator/opendc-simulator-compute/src/main/kotlin/org/opendc/simulator/compute/SimMachineModel.kt b/opendc-simulator/opendc-simulator-compute/src/main/kotlin/org/opendc/simulator/compute/SimMachineModel.kt
new file mode 100644
index 00000000..2b414540
--- /dev/null
+++ b/opendc-simulator/opendc-simulator-compute/src/main/kotlin/org/opendc/simulator/compute/SimMachineModel.kt
@@ -0,0 +1,34 @@
+/*
+ * 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.model.MemoryUnit
+import org.opendc.simulator.compute.model.ProcessingUnit
+
+/**
+ * A description of the physical or virtual machine on which a bootable image runs.
+ *
+ * @property cpus The list of processing units available to the image.
+ * @property memory The list of memory units available to the image.
+ */
+public data class SimMachineModel(public val cpus: List<ProcessingUnit>, public val memory: List<MemoryUnit>)
diff --git a/opendc-simulator/opendc-simulator-compute/src/main/kotlin/org/opendc/simulator/compute/SimProcessingUnit.kt b/opendc-simulator/opendc-simulator-compute/src/main/kotlin/org/opendc/simulator/compute/SimProcessingUnit.kt
new file mode 100644
index 00000000..13c7d9b2
--- /dev/null
+++ b/opendc-simulator/opendc-simulator-compute/src/main/kotlin/org/opendc/simulator/compute/SimProcessingUnit.kt
@@ -0,0 +1,41 @@
+/*
+ * 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.model.ProcessingUnit
+import org.opendc.simulator.resources.SimResourceProvider
+
+/**
+ * A simulated processing unit.
+ */
+public interface SimProcessingUnit : SimResourceProvider {
+ /**
+ * The model representing the static properties of the processing unit.
+ */
+ public val model: ProcessingUnit
+
+ /**
+ * The current speed of the processing unit.
+ */
+ public val speed: Double
+}
diff --git a/opendc-simulator/opendc-simulator-compute/src/main/kotlin/org/opendc/simulator/compute/SimSpaceSharedHypervisor.kt b/opendc-simulator/opendc-simulator-compute/src/main/kotlin/org/opendc/simulator/compute/SimSpaceSharedHypervisor.kt
new file mode 100644
index 00000000..fd8e546f
--- /dev/null
+++ b/opendc-simulator/opendc-simulator-compute/src/main/kotlin/org/opendc/simulator/compute/SimSpaceSharedHypervisor.kt
@@ -0,0 +1,38 @@
+/*
+ * 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.resources.*
+
+/**
+ * A [SimHypervisor] that allocates its sub-resources exclusively for the virtual machine that it hosts.
+ */
+public class SimSpaceSharedHypervisor : SimAbstractHypervisor() {
+ override fun canFit(model: SimMachineModel, switch: SimResourceSwitch): Boolean {
+ return switch.inputs.size - switch.outputs.size >= model.cpus.size
+ }
+
+ override fun createSwitch(ctx: SimMachineContext): SimResourceSwitch {
+ return SimResourceSwitchExclusive()
+ }
+}
diff --git a/opendc-simulator/opendc-simulator-compute/src/main/kotlin/org/opendc/simulator/compute/SimSpaceSharedHypervisorProvider.kt b/opendc-simulator/opendc-simulator-compute/src/main/kotlin/org/opendc/simulator/compute/SimSpaceSharedHypervisorProvider.kt
new file mode 100644
index 00000000..e2044d05
--- /dev/null
+++ b/opendc-simulator/opendc-simulator-compute/src/main/kotlin/org/opendc/simulator/compute/SimSpaceSharedHypervisorProvider.kt
@@ -0,0 +1,32 @@
+/*
+ * 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
+
+/**
+ * A [SimHypervisorProvider] for the [SimSpaceSharedHypervisor] implementation.
+ */
+public class SimSpaceSharedHypervisorProvider : SimHypervisorProvider {
+ override val id: String = "space-shared"
+
+ override fun create(listener: SimHypervisor.Listener?): SimHypervisor = SimSpaceSharedHypervisor()
+}
diff --git a/opendc-simulator/opendc-simulator-compute/src/main/kotlin/org/opendc/simulator/compute/cpufreq/DemandScalingGovernor.kt b/opendc-simulator/opendc-simulator-compute/src/main/kotlin/org/opendc/simulator/compute/cpufreq/DemandScalingGovernor.kt
new file mode 100644
index 00000000..ddbe1ca0
--- /dev/null
+++ b/opendc-simulator/opendc-simulator-compute/src/main/kotlin/org/opendc/simulator/compute/cpufreq/DemandScalingGovernor.kt
@@ -0,0 +1,36 @@
+/*
+ * 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
+
+/**
+ * A CPUFreq [ScalingGovernor] that requests the frequency based on the utilization of the machine.
+ */
+public class DemandScalingGovernor : ScalingGovernor {
+ override fun createLogic(ctx: ScalingContext): ScalingGovernor.Logic = object : ScalingGovernor.Logic {
+ override fun onLimit() {
+ ctx.setTarget(ctx.cpu.speed)
+ }
+
+ override fun toString(): String = "DemandScalingGovernor.Logic"
+ }
+}
diff --git a/opendc-simulator/opendc-simulator-compute/src/main/kotlin/org/opendc/simulator/compute/cpufreq/PStateScalingDriver.kt b/opendc-simulator/opendc-simulator-compute/src/main/kotlin/org/opendc/simulator/compute/cpufreq/PStateScalingDriver.kt
new file mode 100644
index 00000000..6f44d778
--- /dev/null
+++ b/opendc-simulator/opendc-simulator-compute/src/main/kotlin/org/opendc/simulator/compute/cpufreq/PStateScalingDriver.kt
@@ -0,0 +1,86 @@
+/*
+ * 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 org.opendc.simulator.compute.SimMachine
+import org.opendc.simulator.compute.SimProcessingUnit
+import org.opendc.simulator.compute.power.PowerModel
+import java.util.*
+import kotlin.math.max
+import kotlin.math.min
+
+/**
+ * A [ScalingDriver] that scales the frequency of the processor based on a discrete set of frequencies.
+ *
+ * @param states A map describing the states of the driver.
+ */
+public class PStateScalingDriver(states: Map<Double, PowerModel>) : ScalingDriver {
+ /**
+ * The P-States defined by the user and ordered by key.
+ */
+ private val states = TreeMap(states)
+
+ override fun createLogic(machine: SimMachine): ScalingDriver.Logic = object : ScalingDriver.Logic {
+ /**
+ * The scaling contexts.
+ */
+ private val contexts = mutableListOf<ScalingContextImpl>()
+
+ override fun createContext(cpu: SimProcessingUnit): ScalingContext {
+ val ctx = ScalingContextImpl(machine, cpu)
+ contexts.add(ctx)
+ return ctx
+ }
+
+ override fun computePower(): Double {
+ var targetFreq = 0.0
+ var totalSpeed = 0.0
+
+ for (ctx in contexts) {
+ targetFreq = max(ctx.target, targetFreq)
+ totalSpeed += ctx.cpu.speed
+ }
+
+ val maxFreq = states.lastKey()
+ val (actualFreq, model) = states.ceilingEntry(min(maxFreq, targetFreq))
+ val utilization = totalSpeed / (actualFreq * contexts.size)
+ return model.computePower(utilization)
+ }
+
+ override fun toString(): String = "PStateScalingDriver.Logic"
+ }
+
+ private class ScalingContextImpl(
+ override val machine: SimMachine,
+ override val cpu: SimProcessingUnit
+ ) : ScalingContext {
+ var target = cpu.model.frequency
+ private set
+
+ override fun setTarget(freq: Double) {
+ target = freq
+ }
+
+ override fun toString(): String = "PStateScalingDriver.Context"
+ }
+}
diff --git a/opendc-simulator/opendc-simulator-compute/src/main/kotlin/org/opendc/simulator/compute/cpufreq/PerformanceScalingGovernor.kt b/opendc-simulator/opendc-simulator-compute/src/main/kotlin/org/opendc/simulator/compute/cpufreq/PerformanceScalingGovernor.kt
new file mode 100644
index 00000000..96f8775a
--- /dev/null
+++ b/opendc-simulator/opendc-simulator-compute/src/main/kotlin/org/opendc/simulator/compute/cpufreq/PerformanceScalingGovernor.kt
@@ -0,0 +1,36 @@
+/*
+ * 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
+
+/**
+ * A CPUFreq [ScalingGovernor] that causes the highest possible frequency to be requested from the resource.
+ */
+public class PerformanceScalingGovernor : ScalingGovernor {
+ override fun createLogic(ctx: ScalingContext): ScalingGovernor.Logic = object : ScalingGovernor.Logic {
+ override fun onLimit() {
+ ctx.setTarget(ctx.cpu.model.frequency)
+ }
+
+ override fun toString(): String = "PerformanceScalingGovernor.Logic"
+ }
+}
diff --git a/opendc-simulator/opendc-simulator-compute/src/main/kotlin/org/opendc/simulator/compute/cpufreq/ScalingContext.kt b/opendc-simulator/opendc-simulator-compute/src/main/kotlin/org/opendc/simulator/compute/cpufreq/ScalingContext.kt
new file mode 100644
index 00000000..18338079
--- /dev/null
+++ b/opendc-simulator/opendc-simulator-compute/src/main/kotlin/org/opendc/simulator/compute/cpufreq/ScalingContext.kt
@@ -0,0 +1,46 @@
+/*
+ * 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 org.opendc.simulator.compute.SimMachine
+import org.opendc.simulator.compute.SimProcessingUnit
+
+/**
+ * A [ScalingContext] is used to communicate frequency scaling changes between the [ScalingGovernor] and driver.
+ */
+public interface ScalingContext {
+ /**
+ * The machine the processing unit belongs to.
+ */
+ public val machine: SimMachine
+
+ /**
+ * The processing unit associated with this context.
+ */
+ public val cpu: SimProcessingUnit
+
+ /**
+ * Target the processor to run at the specified target [frequency][freq].
+ */
+ public fun setTarget(freq: Double)
+}
diff --git a/opendc-simulator/opendc-simulator-compute/src/main/kotlin/org/opendc/simulator/compute/cpufreq/ScalingDriver.kt b/opendc-simulator/opendc-simulator-compute/src/main/kotlin/org/opendc/simulator/compute/cpufreq/ScalingDriver.kt
new file mode 100644
index 00000000..b4fd7550
--- /dev/null
+++ b/opendc-simulator/opendc-simulator-compute/src/main/kotlin/org/opendc/simulator/compute/cpufreq/ScalingDriver.kt
@@ -0,0 +1,53 @@
+/*
+ * 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 org.opendc.simulator.compute.SimMachine
+import org.opendc.simulator.compute.SimProcessingUnit
+
+/**
+ * A [ScalingDriver] is responsible for switching the processor to the correct frequency.
+ */
+public interface ScalingDriver {
+ /**
+ * Create the scaling logic for the specified [machine]
+ */
+ public fun createLogic(machine: SimMachine): Logic
+
+ /**
+ * The logic of the scaling driver.
+ */
+ public interface Logic {
+ /**
+ * Create the [ScalingContext] for the specified [cpu] instance.
+ */
+ public fun createContext(cpu: SimProcessingUnit): ScalingContext
+
+ /**
+ * Compute the power consumption of the processor.
+ *
+ * @return The power consumption of the processor in W.
+ */
+ public fun computePower(): Double
+ }
+}
diff --git a/opendc-simulator/opendc-simulator-compute/src/main/kotlin/org/opendc/simulator/compute/cpufreq/ScalingGovernor.kt b/opendc-simulator/opendc-simulator-compute/src/main/kotlin/org/opendc/simulator/compute/cpufreq/ScalingGovernor.kt
new file mode 100644
index 00000000..c9aea580
--- /dev/null
+++ b/opendc-simulator/opendc-simulator-compute/src/main/kotlin/org/opendc/simulator/compute/cpufreq/ScalingGovernor.kt
@@ -0,0 +1,54 @@
+/*
+ * 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
+
+/**
+ * A [ScalingGovernor] in the CPUFreq subsystem of OpenDC is responsible for scaling the frequency of simulated CPUs
+ * independent of the particular implementation of the CPU.
+ *
+ * Each of the scaling governors implements a single, possibly parametrized, performance scaling algorithm.
+ *
+ * For more information, see the documentation of the Linux CPUFreq subsystem:
+ * https://www.kernel.org/doc/html/latest/admin-guide/pm/cpufreq.html
+ */
+public interface ScalingGovernor {
+ /**
+ * Create the scaling logic for the specified [context]
+ */
+ public fun createLogic(ctx: ScalingContext): Logic
+
+ /**
+ * The logic of the scaling governor.
+ */
+ public interface Logic {
+ /**
+ * This method is invoked when the governor is started.
+ */
+ public fun onStart() {}
+
+ /**
+ * This method is invoked when the governor should re-decide the frequency limits.
+ */
+ public fun onLimit() {}
+ }
+}
diff --git a/opendc-simulator/opendc-simulator-compute/src/main/kotlin/org/opendc/simulator/compute/cpufreq/SimpleScalingDriver.kt b/opendc-simulator/opendc-simulator-compute/src/main/kotlin/org/opendc/simulator/compute/cpufreq/SimpleScalingDriver.kt
new file mode 100644
index 00000000..cf0bbb28
--- /dev/null
+++ b/opendc-simulator/opendc-simulator-compute/src/main/kotlin/org/opendc/simulator/compute/cpufreq/SimpleScalingDriver.kt
@@ -0,0 +1,49 @@
+/*
+ * 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 org.opendc.simulator.compute.SimMachine
+import org.opendc.simulator.compute.SimProcessingUnit
+import org.opendc.simulator.compute.power.PowerModel
+
+/**
+ * A [ScalingDriver] that ignores the instructions of the [ScalingGovernor] and directly computes the power consumption
+ * based on the specified [power model][model].
+ */
+public class SimpleScalingDriver(private val model: PowerModel) : ScalingDriver {
+ override fun createLogic(machine: SimMachine): ScalingDriver.Logic = object : ScalingDriver.Logic {
+ override fun createContext(cpu: SimProcessingUnit): ScalingContext {
+ return object : ScalingContext {
+ override val machine: SimMachine = machine
+
+ override val cpu: SimProcessingUnit = cpu
+
+ override fun setTarget(freq: Double) {}
+ }
+ }
+
+ override fun computePower(): Double = model.computePower(machine.usage.value)
+
+ override fun toString(): String = "SimpleScalingDriver.Logic"
+ }
+}
diff --git a/opendc-simulator/opendc-simulator-compute/src/main/kotlin/org/opendc/simulator/compute/interference/PerformanceInterferenceModel.kt b/opendc-simulator/opendc-simulator-compute/src/main/kotlin/org/opendc/simulator/compute/interference/PerformanceInterferenceModel.kt
new file mode 100644
index 00000000..4c409887
--- /dev/null
+++ b/opendc-simulator/opendc-simulator-compute/src/main/kotlin/org/opendc/simulator/compute/interference/PerformanceInterferenceModel.kt
@@ -0,0 +1,134 @@
+/*
+ * 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.interference
+
+import java.util.*
+import kotlin.random.Random
+
+/**
+ * Meta-data key for the [PerformanceInterferenceModel] of an image.
+ */
+public const val IMAGE_PERF_INTERFERENCE_MODEL: String = "image:performance-interference"
+
+/**
+ * Performance Interference Model describing the variability incurred by different sets of workloads if colocated.
+ *
+ * @param items The [PerformanceInterferenceModel.Item]s that make up this model.
+ */
+public class PerformanceInterferenceModel(
+ public val items: SortedSet<Item>,
+ private val random: Random = Random(0)
+) {
+ private var intersectingItems: List<Item> = emptyList()
+ private val colocatedWorkloads = TreeMap<String, Int>()
+
+ /**
+ * Indicate that a VM has started.
+ */
+ public fun onStart(name: String) {
+ colocatedWorkloads.merge(name, 1, Int::plus)
+ intersectingItems = items.filter { item -> doesMatch(item) }
+ }
+
+ /**
+ * Indicate that a VM has stopped.
+ */
+ public fun onStop(name: String) {
+ colocatedWorkloads.computeIfPresent(name) { _, v -> (v - 1).takeUnless { it == 0 } }
+ intersectingItems = items.filter { item -> doesMatch(item) }
+ }
+
+ /**
+ * Compute the performance interference based on the current server load.
+ */
+ public fun apply(currentServerLoad: Double): Double {
+ if (intersectingItems.isEmpty()) {
+ return 1.0
+ }
+ val score = intersectingItems
+ .firstOrNull { it.minServerLoad <= currentServerLoad }
+
+ // Apply performance penalty to (on average) only one of the VMs
+ return if (score != null && random.nextInt(score.workloadNames.size) == 0) {
+ score.performanceScore
+ } else {
+ 1.0
+ }
+ }
+
+ private fun doesMatch(item: Item): Boolean {
+ var count = 0
+ for (
+ name in item.workloadNames.subSet(
+ colocatedWorkloads.firstKey(),
+ colocatedWorkloads.lastKey() + "\u0000"
+ )
+ ) {
+ count += colocatedWorkloads.getOrDefault(name, 0)
+ if (count > 1)
+ return true
+ }
+ return false
+ }
+
+ /**
+ * Model describing how a specific set of workloads causes performance variability for each workload.
+ *
+ * @param workloadNames The names of the workloads that together cause performance variability for each workload in the set.
+ * @param minServerLoad The minimum total server load at which this interference is activated and noticeable.
+ * @param performanceScore The performance score that should be applied to each workload's performance. 1 means no
+ * influence, <1 means that performance degrades, and >1 means that performance improves.
+ */
+ public data class Item(
+ public val workloadNames: SortedSet<String>,
+ public val minServerLoad: Double,
+ public val performanceScore: Double
+ ) : Comparable<Item> {
+ override fun equals(other: Any?): Boolean {
+ if (this === other) return true
+ if (javaClass != other?.javaClass) return false
+
+ other as Item
+
+ if (workloadNames != other.workloadNames) return false
+
+ return true
+ }
+
+ override fun hashCode(): Int = workloadNames.hashCode()
+
+ override fun compareTo(other: Item): Int {
+ var cmp = performanceScore.compareTo(other.performanceScore)
+ if (cmp != 0) {
+ return cmp
+ }
+
+ cmp = minServerLoad.compareTo(other.minServerLoad)
+ if (cmp != 0) {
+ return cmp
+ }
+
+ return hashCode().compareTo(other.hashCode())
+ }
+ }
+}
diff --git a/opendc-simulator/opendc-simulator-compute/src/main/kotlin/org/opendc/simulator/compute/model/MemoryUnit.kt b/opendc-simulator/opendc-simulator-compute/src/main/kotlin/org/opendc/simulator/compute/model/MemoryUnit.kt
new file mode 100644
index 00000000..bcbde5b1
--- /dev/null
+++ b/opendc-simulator/opendc-simulator-compute/src/main/kotlin/org/opendc/simulator/compute/model/MemoryUnit.kt
@@ -0,0 +1,38 @@
+/*
+ * 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.model
+
+/**
+ * A memory unit of a compute resource, either virtual or physical.
+ *
+ * @property vendor The vendor string of the memory.
+ * @property modelName The name of the memory model.
+ * @property speed The access speed of the memory in MHz.
+ * @property size The size of the memory unit in MBs.
+ */
+public data class MemoryUnit(
+ public val vendor: String,
+ public val modelName: String,
+ public val speed: Double,
+ public val size: Long
+)
diff --git a/opendc-simulator/opendc-simulator-compute/src/main/kotlin/org/opendc/simulator/compute/model/ProcessingNode.kt b/opendc-simulator/opendc-simulator-compute/src/main/kotlin/org/opendc/simulator/compute/model/ProcessingNode.kt
new file mode 100644
index 00000000..58ed816c
--- /dev/null
+++ b/opendc-simulator/opendc-simulator-compute/src/main/kotlin/org/opendc/simulator/compute/model/ProcessingNode.kt
@@ -0,0 +1,38 @@
+/*
+ * 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.model
+
+/**
+ * A processing node/package/socket containing possibly several CPU cores.
+ *
+ * @property vendor The vendor string of the processor node.
+ * @property modelName The name of the processor node.
+ * @property arch The micro-architecture of the processor node.
+ * @property coreCount The number of logical CPUs in the processor node.
+ */
+public data class ProcessingNode(
+ public val vendor: String,
+ public val arch: String,
+ public val modelName: String,
+ public val coreCount: Int
+)
diff --git a/opendc-simulator/opendc-simulator-compute/src/main/kotlin/org/opendc/simulator/compute/model/ProcessingUnit.kt b/opendc-simulator/opendc-simulator-compute/src/main/kotlin/org/opendc/simulator/compute/model/ProcessingUnit.kt
new file mode 100644
index 00000000..415e95e6
--- /dev/null
+++ b/opendc-simulator/opendc-simulator-compute/src/main/kotlin/org/opendc/simulator/compute/model/ProcessingUnit.kt
@@ -0,0 +1,36 @@
+/*
+ * 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.model
+
+/**
+ * A single logical compute unit of processor node, either virtual or physical.
+ *
+ * @property node The processing node containing the CPU core.
+ * @property id The identifier of the CPU core within the processing node.
+ * @property frequency The clock rate of the CPU in MHz.
+ */
+public data class ProcessingUnit(
+ public val node: ProcessingNode,
+ public val id: Int,
+ public val frequency: Double
+)
diff --git a/opendc-simulator/opendc-simulator-compute/src/main/kotlin/org/opendc/simulator/compute/power/AsymptoticPowerModel.kt b/opendc-simulator/opendc-simulator-compute/src/main/kotlin/org/opendc/simulator/compute/power/AsymptoticPowerModel.kt
new file mode 100644
index 00000000..ddc6d5b1
--- /dev/null
+++ b/opendc-simulator/opendc-simulator-compute/src/main/kotlin/org/opendc/simulator/compute/power/AsymptoticPowerModel.kt
@@ -0,0 +1,31 @@
+package org.opendc.simulator.compute.power
+
+import kotlin.math.E
+import kotlin.math.pow
+
+/**
+ * The asymptotic power model partially adapted from GreenCloud.
+ *
+ * @param maxPower The maximum power draw of the server in W.
+ * @param idlePower The power draw of the server at its lowest utilization level in W.
+ * @param asymUtil A utilization level at which the server attains asymptotic,
+ * i.e., close to linear power consumption versus the offered load.
+ * For most of the CPUs,a is in [0.2, 0.5].
+ * @param isDvfsEnabled A flag indicates whether DVFS is enabled.
+ */
+public class AsymptoticPowerModel(
+ private val maxPower: Double,
+ private val idlePower: Double,
+ private val asymUtil: Double,
+ private val isDvfsEnabled: Boolean,
+) : PowerModel {
+ private val factor: Double = (maxPower - idlePower) / 100
+
+ public override fun computePower(utilization: Double): Double =
+ if (isDvfsEnabled)
+ idlePower + (factor * 100) / 2 * (1 + utilization.pow(3) - E.pow(-utilization.pow(3) / asymUtil))
+ else
+ idlePower + (factor * 100) / 2 * (1 + utilization - E.pow(-utilization / asymUtil))
+
+ override fun toString(): String = "AsymptoticPowerModel[max=$maxPower,idle=$idlePower,asymptotic=$asymUtil]"
+}
diff --git a/opendc-simulator/opendc-simulator-compute/src/main/kotlin/org/opendc/simulator/compute/power/ConstantPowerModel.kt b/opendc-simulator/opendc-simulator-compute/src/main/kotlin/org/opendc/simulator/compute/power/ConstantPowerModel.kt
new file mode 100644
index 00000000..b8cb8412
--- /dev/null
+++ b/opendc-simulator/opendc-simulator-compute/src/main/kotlin/org/opendc/simulator/compute/power/ConstantPowerModel.kt
@@ -0,0 +1,10 @@
+package org.opendc.simulator.compute.power
+
+/**
+ * A power model which produces a constant value [power].
+ */
+public class ConstantPowerModel(private val power: Double) : PowerModel {
+ public override fun computePower(utilization: Double): Double = power
+
+ override fun toString(): String = "ConstantPowerModel[power=$power]"
+}
diff --git a/opendc-simulator/opendc-simulator-compute/src/main/kotlin/org/opendc/simulator/compute/power/CubicPowerModel.kt b/opendc-simulator/opendc-simulator-compute/src/main/kotlin/org/opendc/simulator/compute/power/CubicPowerModel.kt
new file mode 100644
index 00000000..9c44438a
--- /dev/null
+++ b/opendc-simulator/opendc-simulator-compute/src/main/kotlin/org/opendc/simulator/compute/power/CubicPowerModel.kt
@@ -0,0 +1,19 @@
+package org.opendc.simulator.compute.power
+
+import kotlin.math.pow
+
+/**
+ * The cubic power model partially adapted from CloudSim.
+ *
+ * @param maxPower The maximum power draw of the server in W.
+ * @param idlePower The power draw of the server at its lowest utilization level in W.
+ */
+public class CubicPowerModel(private val maxPower: Double, private val idlePower: Double) : PowerModel {
+ private val factor: Double = (maxPower - idlePower) / 100.0.pow(3)
+
+ public override fun computePower(utilization: Double): Double {
+ return idlePower + factor * (utilization * 100).pow(3)
+ }
+
+ override fun toString(): String = "CubicPowerModel[max=$maxPower,idle=$idlePower]"
+}
diff --git a/opendc-simulator/opendc-simulator-compute/src/main/kotlin/org/opendc/simulator/compute/power/InterpolationPowerModel.kt b/opendc-simulator/opendc-simulator-compute/src/main/kotlin/org/opendc/simulator/compute/power/InterpolationPowerModel.kt
new file mode 100644
index 00000000..cbfcd810
--- /dev/null
+++ b/opendc-simulator/opendc-simulator-compute/src/main/kotlin/org/opendc/simulator/compute/power/InterpolationPowerModel.kt
@@ -0,0 +1,54 @@
+package org.opendc.simulator.compute.power
+
+import org.yaml.snakeyaml.Yaml
+import kotlin.math.ceil
+import kotlin.math.floor
+import kotlin.math.max
+import kotlin.math.min
+
+/**
+ * The linear interpolation power model partially adapted from CloudSim.
+ * This model is developed to adopt the <a href="http://www.spec.org/power_ssj2008/">SPECpower benchmark</a>.
+ *
+ * @param powerValues A [List] of average active power measured by the power analyzer(s) and accumulated by the
+ * PTDaemon (Power and Temperature Daemon) for this measurement interval, displayed as watts (W).
+ * @see <a href="http://www.spec.org/power_ssj2008/results/res2011q1/">Machines used in the SPEC benchmark</a>
+ */
+public class InterpolationPowerModel(private val powerValues: List<Double>) : PowerModel {
+ public constructor(hardwareName: String) : this(loadAveragePowerValue(hardwareName))
+
+ public override fun computePower(utilization: Double): Double {
+ val clampedUtilization = min(1.0, max(0.0, utilization))
+ val utilizationFlr = floor(clampedUtilization * 10).toInt()
+ val utilizationCil = ceil(clampedUtilization * 10).toInt()
+ val powerFlr: Double = getAveragePowerValue(utilizationFlr)
+ val powerCil: Double = getAveragePowerValue(utilizationCil)
+ val delta = (powerCil - powerFlr) / 10
+
+ return if (utilization % 0.1 == 0.0)
+ getAveragePowerValue((clampedUtilization * 10).toInt())
+ else
+ powerFlr + delta * (clampedUtilization - utilizationFlr.toDouble() / 10) * 100
+ }
+
+ override fun toString(): String = "InterpolationPowerModel[entries=${powerValues.size}]"
+
+ /**
+ * Gets the power consumption for a given utilization percentage.
+ *
+ * @param index the utilization percentage in the scale from [0 to 10],
+ * where 10 means 100% of utilization.
+ * @return the power consumption for the given utilization percentage
+ */
+ private fun getAveragePowerValue(index: Int): Double = powerValues[index]
+
+ private companion object {
+ private fun loadAveragePowerValue(hardwareName: String, path: String = "spec_machines.yml"): List<Double> {
+ val content = this::class
+ .java.classLoader
+ .getResourceAsStream(path)
+ val hardwareToAveragePowerValues: Map<String, List<Double>> = Yaml().load(content)
+ return hardwareToAveragePowerValues.getOrDefault(hardwareName, listOf())
+ }
+ }
+}
diff --git a/opendc-simulator/opendc-simulator-compute/src/main/kotlin/org/opendc/simulator/compute/power/LinearPowerModel.kt b/opendc-simulator/opendc-simulator-compute/src/main/kotlin/org/opendc/simulator/compute/power/LinearPowerModel.kt
new file mode 100644
index 00000000..0f45afae
--- /dev/null
+++ b/opendc-simulator/opendc-simulator-compute/src/main/kotlin/org/opendc/simulator/compute/power/LinearPowerModel.kt
@@ -0,0 +1,20 @@
+package org.opendc.simulator.compute.power
+
+/**
+ * The linear power model partially adapted from CloudSim.
+ *
+ * @param maxPower The maximum power draw of the server in W.
+ * @param idlePower The power draw of the server at its lowest utilization level in W.
+ */
+public class LinearPowerModel(private val maxPower: Double, private val idlePower: Double) : PowerModel {
+ /**
+ * The linear interpolation factor of the model.
+ */
+ private val factor: Double = (maxPower - idlePower) / 100
+
+ public override fun computePower(utilization: Double): Double {
+ return idlePower + factor * utilization * 100
+ }
+
+ override fun toString(): String = "LinearPowerModel[max=$maxPower,idle=$idlePower]"
+}
diff --git a/opendc-simulator/opendc-simulator-compute/src/main/kotlin/org/opendc/simulator/compute/power/MsePowerModel.kt b/opendc-simulator/opendc-simulator-compute/src/main/kotlin/org/opendc/simulator/compute/power/MsePowerModel.kt
new file mode 100644
index 00000000..8486d680
--- /dev/null
+++ b/opendc-simulator/opendc-simulator-compute/src/main/kotlin/org/opendc/simulator/compute/power/MsePowerModel.kt
@@ -0,0 +1,27 @@
+package org.opendc.simulator.compute.power
+
+import kotlin.math.pow
+
+/**
+ * The power model that minimizes the mean squared error (MSE)
+ * to the actual power measurement by tuning the calibration parameter.
+ * @see <a href="https://dl.acm.org/doi/abs/10.1145/1273440.1250665">
+ * Fan et al., Power provisioning for a warehouse-sized computer, ACM SIGARCH'07</a>
+ *
+ * @param maxPower The maximum power draw of the server in W.
+ * @param idlePower The power draw of the server at its lowest utilization level in W.
+ * @param calibrationParam The parameter set to minimize the MSE.
+ */
+public class MsePowerModel(
+ private val maxPower: Double,
+ private val idlePower: Double,
+ private val calibrationParam: Double,
+) : PowerModel {
+ private val factor: Double = (maxPower - idlePower) / 100
+
+ public override fun computePower(utilization: Double): Double {
+ return idlePower + factor * (2 * utilization - utilization.pow(calibrationParam)) * 100
+ }
+
+ override fun toString(): String = "MsePowerModel[max=$maxPower,idle=$idlePower,MSE_param=$calibrationParam]"
+}
diff --git a/opendc-simulator/opendc-simulator-compute/src/main/kotlin/org/opendc/simulator/compute/power/PowerModel.kt b/opendc-simulator/opendc-simulator-compute/src/main/kotlin/org/opendc/simulator/compute/power/PowerModel.kt
new file mode 100644
index 00000000..1387e65a
--- /dev/null
+++ b/opendc-simulator/opendc-simulator-compute/src/main/kotlin/org/opendc/simulator/compute/power/PowerModel.kt
@@ -0,0 +1,16 @@
+package org.opendc.simulator.compute.power
+
+import org.opendc.simulator.compute.SimMachine
+
+/**
+ * A model for estimating the power usage of a [SimMachine].
+ */
+public interface PowerModel {
+ /**
+ * Computes CPU power consumption for each host.
+ *
+ * @param utilization The CPU utilization percentage.
+ * @return A [Double] value of CPU power consumption.
+ */
+ public fun computePower(utilization: Double): Double
+}
diff --git a/opendc-simulator/opendc-simulator-compute/src/main/kotlin/org/opendc/simulator/compute/power/SqrtPowerModel.kt b/opendc-simulator/opendc-simulator-compute/src/main/kotlin/org/opendc/simulator/compute/power/SqrtPowerModel.kt
new file mode 100644
index 00000000..afa1d82f
--- /dev/null
+++ b/opendc-simulator/opendc-simulator-compute/src/main/kotlin/org/opendc/simulator/compute/power/SqrtPowerModel.kt
@@ -0,0 +1,19 @@
+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 of the server in W.
+ * @param idlePower The power draw of the server at its lowest utilization level in W.
+ */
+public class SqrtPowerModel(private val maxPower: Double, private val idlePower: Double) : PowerModel {
+ private val factor: Double = (maxPower - idlePower) / sqrt(100.0)
+
+ override fun computePower(utilization: Double): Double {
+ return idlePower + factor * sqrt(utilization * 100)
+ }
+
+ override fun toString(): String = "SqrtPowerModel[max=$maxPower,idle=$idlePower]"
+}
diff --git a/opendc-simulator/opendc-simulator-compute/src/main/kotlin/org/opendc/simulator/compute/power/SquarePowerModel.kt b/opendc-simulator/opendc-simulator-compute/src/main/kotlin/org/opendc/simulator/compute/power/SquarePowerModel.kt
new file mode 100644
index 00000000..82a9d37d
--- /dev/null
+++ b/opendc-simulator/opendc-simulator-compute/src/main/kotlin/org/opendc/simulator/compute/power/SquarePowerModel.kt
@@ -0,0 +1,19 @@
+package org.opendc.simulator.compute.power
+
+import kotlin.math.pow
+
+/**
+ * The square power model partially adapted from CloudSim.
+ *
+ * @param maxPower The maximum power draw of the server in W.
+ * @param idlePower The power draw of the server at its lowest utilization level in W.
+ */
+public class SquarePowerModel(private val maxPower: Double, private val idlePower: Double) : PowerModel {
+ private val factor: Double = (maxPower - idlePower) / 100.0.pow(2)
+
+ override fun computePower(utilization: Double): Double {
+ return idlePower + factor * (utilization * 100).pow(2)
+ }
+
+ override fun toString(): String = "SquarePowerModel[max=$maxPower,idle=$idlePower]"
+}
diff --git a/opendc-simulator/opendc-simulator-compute/src/main/kotlin/org/opendc/simulator/compute/power/ZeroIdlePowerDecorator.kt b/opendc-simulator/opendc-simulator-compute/src/main/kotlin/org/opendc/simulator/compute/power/ZeroIdlePowerDecorator.kt
new file mode 100644
index 00000000..19dfcadd
--- /dev/null
+++ b/opendc-simulator/opendc-simulator-compute/src/main/kotlin/org/opendc/simulator/compute/power/ZeroIdlePowerDecorator.kt
@@ -0,0 +1,17 @@
+package org.opendc.simulator.compute.power
+
+/**
+ * A decorator for ignoring the idle power when computing energy consumption of components.
+ *
+ * @param delegate The [PowerModel] to delegate to.
+ */
+public class ZeroIdlePowerDecorator(private val delegate: PowerModel) : PowerModel {
+ override fun computePower(utilization: Double): Double {
+ return if (utilization == 0.0)
+ 0.0
+ else
+ delegate.computePower(utilization)
+ }
+
+ override fun toString(): String = "ZeroIdlePowerDecorator[delegate=$delegate]"
+}
diff --git a/opendc-simulator/opendc-simulator-compute/src/main/kotlin/org/opendc/simulator/compute/workload/SimFlopsWorkload.kt b/opendc-simulator/opendc-simulator-compute/src/main/kotlin/org/opendc/simulator/compute/workload/SimFlopsWorkload.kt
new file mode 100644
index 00000000..63c9d28c
--- /dev/null
+++ b/opendc-simulator/opendc-simulator-compute/src/main/kotlin/org/opendc/simulator/compute/workload/SimFlopsWorkload.kt
@@ -0,0 +1,53 @@
+/*
+ * 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.opendc.simulator.compute.SimMachineContext
+import org.opendc.simulator.compute.model.ProcessingUnit
+import org.opendc.simulator.resources.SimResourceConsumer
+import org.opendc.simulator.resources.consumer.SimWorkConsumer
+
+/**
+ * A [SimWorkload] that models applications as a static number of floating point operations ([flops]) executed on
+ * multiple cores of a compute resource.
+ *
+ * @property flops The number of floating point operations to perform for this task in MFLOPs.
+ * @property utilization A model of the CPU utilization of the application.
+ */
+public class SimFlopsWorkload(
+ public val flops: Long,
+ public val utilization: Double = 0.8
+) : SimWorkload {
+ init {
+ require(flops >= 0) { "Number of FLOPs must be positive" }
+ require(utilization > 0.0 && utilization <= 1.0) { "Utilization must be in (0, 1]" }
+ }
+
+ override fun onStart(ctx: SimMachineContext) {}
+
+ override fun getConsumer(ctx: SimMachineContext, cpu: ProcessingUnit): SimResourceConsumer {
+ return SimWorkConsumer(flops.toDouble() / ctx.cpus.size, utilization)
+ }
+
+ override fun toString(): String = "SimFlopsWorkload(FLOPs=$flops,utilization=$utilization)"
+}
diff --git a/opendc-simulator/opendc-simulator-compute/src/main/kotlin/org/opendc/simulator/compute/workload/SimRuntimeWorkload.kt b/opendc-simulator/opendc-simulator-compute/src/main/kotlin/org/opendc/simulator/compute/workload/SimRuntimeWorkload.kt
new file mode 100644
index 00000000..a3420e32
--- /dev/null
+++ b/opendc-simulator/opendc-simulator-compute/src/main/kotlin/org/opendc/simulator/compute/workload/SimRuntimeWorkload.kt
@@ -0,0 +1,53 @@
+/*
+ * 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.opendc.simulator.compute.SimMachineContext
+import org.opendc.simulator.compute.model.ProcessingUnit
+import org.opendc.simulator.resources.SimResourceConsumer
+import org.opendc.simulator.resources.consumer.SimWorkConsumer
+
+/**
+ * A [SimWorkload] that models application execution as a single duration.
+ *
+ * @property duration The duration of the workload.
+ * @property utilization The utilization of the application during runtime.
+ */
+public class SimRuntimeWorkload(
+ public val duration: Long,
+ public val utilization: Double = 0.8
+) : SimWorkload {
+ init {
+ require(duration >= 0) { "Duration must be non-negative" }
+ require(utilization > 0.0 && utilization <= 1.0) { "Utilization must be in (0, 1]" }
+ }
+
+ override fun onStart(ctx: SimMachineContext) {}
+
+ override fun getConsumer(ctx: SimMachineContext, cpu: ProcessingUnit): SimResourceConsumer {
+ val limit = cpu.frequency * utilization
+ return SimWorkConsumer((limit / 1000) * duration, utilization)
+ }
+
+ override fun toString(): String = "SimRuntimeWorkload(duration=$duration,utilization=$utilization)"
+}
diff --git a/opendc-simulator/opendc-simulator-compute/src/main/kotlin/org/opendc/simulator/compute/workload/SimTraceWorkload.kt b/opendc-simulator/opendc-simulator-compute/src/main/kotlin/org/opendc/simulator/compute/workload/SimTraceWorkload.kt
new file mode 100644
index 00000000..ffb332d1
--- /dev/null
+++ b/opendc-simulator/opendc-simulator-compute/src/main/kotlin/org/opendc/simulator/compute/workload/SimTraceWorkload.kt
@@ -0,0 +1,94 @@
+/*
+ * 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.opendc.simulator.compute.SimMachineContext
+import org.opendc.simulator.compute.model.ProcessingUnit
+import org.opendc.simulator.resources.SimResourceCommand
+import org.opendc.simulator.resources.SimResourceConsumer
+import org.opendc.simulator.resources.SimResourceContext
+import org.opendc.simulator.resources.consumer.SimConsumerBarrier
+
+/**
+ * A [SimWorkload] that replays a workload trace consisting of multiple fragments, each indicating the resource
+ * consumption for some period of time.
+ */
+public class SimTraceWorkload(public val trace: Sequence<Fragment>) : SimWorkload {
+ private var offset = Long.MIN_VALUE
+ private val iterator = trace.iterator()
+ private var fragment: Fragment? = null
+ private lateinit var barrier: SimConsumerBarrier
+
+ override fun onStart(ctx: SimMachineContext) {
+ check(offset == Long.MIN_VALUE) { "Workload does not support re-use" }
+
+ barrier = SimConsumerBarrier(ctx.cpus.size)
+ fragment = nextFragment()
+ offset = ctx.clock.millis()
+ }
+
+ override fun getConsumer(ctx: SimMachineContext, cpu: ProcessingUnit): SimResourceConsumer {
+ return object : SimResourceConsumer {
+ override fun onNext(ctx: SimResourceContext): SimResourceCommand {
+ val now = ctx.clock.millis()
+ val fragment = fragment ?: return SimResourceCommand.Exit
+ val usage = fragment.usage / fragment.cores
+ val work = (fragment.duration / 1000) * usage
+ val deadline = offset + fragment.duration
+
+ assert(deadline >= now) { "Deadline already passed" }
+
+ val cmd =
+ if (cpu.id < fragment.cores && work > 0.0)
+ SimResourceCommand.Consume(work, usage, deadline)
+ else
+ SimResourceCommand.Idle(deadline)
+
+ if (barrier.enter()) {
+ this@SimTraceWorkload.fragment = nextFragment()
+ this@SimTraceWorkload.offset += fragment.duration
+ }
+
+ return cmd
+ }
+ }
+ }
+
+ override fun toString(): String = "SimTraceWorkload"
+
+ /**
+ * Obtain the next fragment.
+ */
+ private fun nextFragment(): Fragment? {
+ return if (iterator.hasNext()) {
+ iterator.next()
+ } else {
+ null
+ }
+ }
+
+ /**
+ * A fragment of the workload.
+ */
+ public data class Fragment(val duration: Long, val usage: Double, val cores: Int)
+}
diff --git a/opendc-simulator/opendc-simulator-compute/src/main/kotlin/org/opendc/simulator/compute/workload/SimWorkload.kt b/opendc-simulator/opendc-simulator-compute/src/main/kotlin/org/opendc/simulator/compute/workload/SimWorkload.kt
new file mode 100644
index 00000000..bdc12bb5
--- /dev/null
+++ b/opendc-simulator/opendc-simulator-compute/src/main/kotlin/org/opendc/simulator/compute/workload/SimWorkload.kt
@@ -0,0 +1,45 @@
+/*
+ * 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.opendc.simulator.compute.SimMachineContext
+import org.opendc.simulator.compute.model.ProcessingUnit
+import org.opendc.simulator.resources.SimResourceConsumer
+
+/**
+ * A model that characterizes the runtime behavior of some particular workload.
+ *
+ * Workloads are stateful objects that may be paused and resumed at a later moment. As such, be careful when using the
+ * same [SimWorkload] from multiple contexts.
+ */
+public interface SimWorkload {
+ /**
+ * This method is invoked when the workload is started.
+ */
+ public fun onStart(ctx: SimMachineContext)
+
+ /**
+ * Obtain the resource consumer for the specified processing unit.
+ */
+ public fun getConsumer(ctx: SimMachineContext, cpu: ProcessingUnit): SimResourceConsumer
+}
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]