diff options
| author | Fabian Mastenbroek <mail.fabianm@gmail.com> | 2021-06-03 14:03:12 +0200 |
|---|---|---|
| committer | GitHub <noreply@github.com> | 2021-06-03 14:03:12 +0200 |
| commit | 1303fe97510fb7987746722b3261c696f523fbd5 (patch) | |
| tree | d927dbd4c71a5ea6435f5994e8fa0bc90ef19b2c /opendc-simulator/opendc-simulator-compute/src | |
| parent | ae987fa84b2e061eb9fdfec5561e1c976aaa5a54 (diff) | |
| parent | cef12722f03a24a0e1e3b7502fb5e434d93f1664 (diff) | |
simulator: Improve simulator resource model (#142)
This pull request improves the existing simulator resource model
that is at the core of all simulation models in OpenDC.
Most importantly, we have changed the way of how metrics are reported by this layer.
* Add `SimResourceInterpreter` which is responsible for efficiently scheduling
communication between resources in OpenDC. The performance gain is in the 2x-5x range.
* Add uniform interface for exposing resource metrics (using `SimResourceCounters`).
* Split the CPUFreq subsystem in the compute simulator as it mixed responsibilities of
different layers.
**Breaking API Changes**
* Resource providers now accept a `SimResourceInterpreter` which is
responsible for coordinating the communication between resources.
* `ScalingGovernor` is not part of the machine layer anymore. Instead, it should
move in the OS/Hypervisor layer.
* Workloads should now start CPU consumers using `cpu.startConsumer`.
Diffstat (limited to 'opendc-simulator/opendc-simulator-compute/src')
28 files changed, 551 insertions, 555 deletions
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 index 15714aca..fb753de2 100644 --- 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 @@ -25,17 +25,15 @@ 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.power.SimplePowerDriver import org.opendc.simulator.compute.workload.SimTraceWorkload import org.opendc.simulator.core.SimulationCoroutineScope import org.opendc.simulator.core.runBlockingSimulation -import org.opendc.simulator.resources.SimResourceScheduler -import org.opendc.simulator.resources.SimResourceSchedulerTrampoline +import org.opendc.simulator.resources.SimResourceInterpreter import org.openjdk.jmh.annotations.* import java.util.concurrent.TimeUnit @@ -46,13 +44,13 @@ import java.util.concurrent.TimeUnit @OptIn(ExperimentalCoroutinesApi::class) class SimMachineBenchmarks { private lateinit var scope: SimulationCoroutineScope - private lateinit var scheduler: SimResourceScheduler + private lateinit var interpreter: SimResourceInterpreter private lateinit var machineModel: SimMachineModel @Setup fun setUp() { scope = SimulationCoroutineScope() - scheduler = SimResourceSchedulerTrampoline(scope.coroutineContext, scope.clock) + interpreter = SimResourceInterpreter(scope.coroutineContext, scope.clock) val cpuNode = ProcessingNode("Intel", "Xeon", "amd64", 2) @@ -85,8 +83,7 @@ class SimMachineBenchmarks { fun benchmarkBareMetal(state: Workload) { return scope.runBlockingSimulation { val machine = SimBareMetalMachine( - coroutineContext, clock, machineModel, PerformanceScalingGovernor(), - SimpleScalingDriver(ConstantPowerModel(0.0)) + interpreter, machineModel, SimplePowerDriver(ConstantPowerModel(0.0)) ) return@runBlockingSimulation machine.run(SimTraceWorkload(state.trace)) } @@ -96,10 +93,9 @@ class SimMachineBenchmarks { fun benchmarkSpaceSharedHypervisor(state: Workload) { return scope.runBlockingSimulation { val machine = SimBareMetalMachine( - coroutineContext, clock, machineModel, PerformanceScalingGovernor(), - SimpleScalingDriver(ConstantPowerModel(0.0)) + interpreter, machineModel, SimplePowerDriver(ConstantPowerModel(0.0)) ) - val hypervisor = SimSpaceSharedHypervisor() + val hypervisor = SimSpaceSharedHypervisor(interpreter) launch { machine.run(hypervisor) } @@ -118,10 +114,9 @@ class SimMachineBenchmarks { fun benchmarkFairShareHypervisorSingle(state: Workload) { return scope.runBlockingSimulation { val machine = SimBareMetalMachine( - coroutineContext, clock, machineModel, PerformanceScalingGovernor(), - SimpleScalingDriver(ConstantPowerModel(0.0)) + interpreter, machineModel, SimplePowerDriver(ConstantPowerModel(0.0)) ) - val hypervisor = SimFairShareHypervisor(scheduler) + val hypervisor = SimFairShareHypervisor(interpreter) launch { machine.run(hypervisor) } @@ -140,10 +135,9 @@ class SimMachineBenchmarks { fun benchmarkFairShareHypervisorDouble(state: Workload) { return scope.runBlockingSimulation { val machine = SimBareMetalMachine( - coroutineContext, clock, machineModel, PerformanceScalingGovernor(), - SimpleScalingDriver(ConstantPowerModel(0.0)) + interpreter, machineModel, SimplePowerDriver(ConstantPowerModel(0.0)) ) - val hypervisor = SimFairShareHypervisor(scheduler) + val hypervisor = SimFairShareHypervisor(interpreter) launch { machine.run(hypervisor) } 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 index 713376e7..57c25b86 100644 --- 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 @@ -22,21 +22,22 @@ 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.cpufreq.ScalingGovernor 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 +import org.opendc.simulator.resources.SimResourceSwitch /** * Abstract implementation of the [SimHypervisor] interface. + * + * @param interpreter The resource interpreter to use. + * @param scalingGovernor The scaling governor to use for scaling the CPU frequency of the underlying hardware. */ -public abstract class SimAbstractHypervisor : SimHypervisor { +public abstract class SimAbstractHypervisor( + private val interpreter: SimResourceInterpreter, + private val scalingGovernor: ScalingGovernor? +) : SimHypervisor { /** * The machine on which the hypervisor runs. */ @@ -55,6 +56,11 @@ public abstract class SimAbstractHypervisor : SimHypervisor { get() = _vms /** + * The scaling governors attached to the physical CPUs backing this hypervisor. + */ + private val governors = mutableListOf<ScalingGovernor.Logic>() + + /** * Construct the [SimResourceSwitch] implementation that performs the actual scheduling of the CPUs. */ public abstract fun createSwitch(ctx: SimMachineContext): SimResourceSwitch @@ -64,6 +70,16 @@ public abstract class SimAbstractHypervisor : SimHypervisor { */ public abstract fun canFit(model: SimMachineModel, switch: SimResourceSwitch): Boolean + /** + * Trigger the governors to recompute the scaling limits. + */ + protected fun triggerGovernors(load: Double) { + for (governor in governors) { + governor.onLimit(load) + } + } + + /* SimHypervisor */ override fun canFit(model: SimMachineModel): Boolean { return canFit(model, switch) } @@ -78,6 +94,21 @@ public abstract class SimAbstractHypervisor : SimHypervisor { return vm } + /* SimWorkload */ + override fun onStart(ctx: SimMachineContext) { + context = ctx + switch = createSwitch(ctx) + + for (cpu in ctx.cpus) { + val governor = scalingGovernor?.createLogic(cpu) + if (governor != null) { + governors.add(governor) + governor.onStart() + } + switch.addInput(cpu) + } + } + /** * A virtual machine running on the hypervisor. * @@ -85,105 +116,34 @@ public abstract class SimAbstractHypervisor : SimHypervisor { * @property performanceInterferenceModel The performance interference model to utilize. */ private inner class VirtualMachine( - override val model: SimMachineModel, + 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 - + ) : SimAbstractMachine(interpreter, parent = null, model) { /** * 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 cpus = model.cpus.map { VCpu(switch.newOutput(), it) } - 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 + super.close() - cpus.forEach(SimProcessingUnit::close) - _vms.remove(this) - } + _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. + * A [SimProcessingUnit] of a virtual 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() - } + private class VCpu( + private val source: SimResourceProvider, + override val model: ProcessingUnit + ) : SimProcessingUnit, SimResourceProvider by source { + override var capacity: Double + get() = source.capacity + set(_) { + // Ignore capacity changes + } - override fun close() { - source.close() - } + override fun toString(): String = "SimAbstractHypervisor.VCpu[model=$model]" } } 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 index e501033a..e12ac72b 100644 --- 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 @@ -27,17 +27,27 @@ 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 +import org.opendc.simulator.resources.* +import kotlin.coroutines.Continuation +import kotlin.coroutines.resume /** * Abstract implementation of the [SimMachine] interface. + * + * @param interpreter The interpreter to manage the machine's resources. + * @param parent The parent simulation system. + * @param model The model of the machine. */ -public abstract class SimAbstractMachine(private val clock: Clock) : SimMachine { +public abstract class SimAbstractMachine( + protected val interpreter: SimResourceInterpreter, + final override val parent: SimResourceSystem?, + final override val model: SimMachineModel +) : SimMachine, SimResourceSystem { + /** + * A [StateFlow] representing the CPU usage of the simulated machine. + */ private val _usage = MutableStateFlow(0.0) - override val usage: StateFlow<Double> + public final override val usage: StateFlow<Double> get() = _usage /** @@ -48,67 +58,76 @@ public abstract class SimAbstractMachine(private val clock: Clock) : SimMachine 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. + * A flag to indicate that the machine is terminated. */ - 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 + private var isTerminated = false - override val memory: List<MemoryUnit> = model.memory - } + /** + * The continuation to resume when the virtual machine workload has finished. + */ + private var cont: Continuation<Unit>? = null /** * 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.sumOf { it.frequency } + override suspend fun run(workload: SimWorkload, meta: Map<String, Any>) { + check(!isTerminated) { "Machine is terminated" } + check(cont == null) { "A machine cannot run concurrently" } - _speed = DoubleArray(model.cpus.size) { 0.0 } - var totalSpeed = 0.0 + val ctx = Context(meta) // Before the workload starts, initialize the initial power draw + _speed = DoubleArray(model.cpus.size) { 0.0 } updateUsage(0.0) - workload.onStart(ctx) + return suspendCancellableCoroutine { cont -> + this.cont = cont - 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) + // Cancel all cpus on cancellation + cont.invokeOnCancellation { + this.cont = null + + interpreter.batch { + for (cpu in cpus) { + cpu.cancel() + } } } - launch { cpu.consume(adapter) } + interpreter.batch { workload.onStart(ctx) } + } + } + + override fun close() { + if (isTerminated) { + return + } + + isTerminated = true + cancel() + interpreter.batch { + for (cpu in cpus) { + cpu.close() + } + } + } + + /* SimResourceSystem */ + override fun onConverge(timestamp: Long) { + val totalCapacity = model.cpus.sumOf { it.frequency } + val cpus = cpus + var totalSpeed = 0.0 + for (cpu in cpus) { + _speed[cpu.model.id] = cpu.speed + totalSpeed += cpu.speed } + + updateUsage(totalSpeed / totalCapacity) } /** @@ -118,10 +137,34 @@ public abstract class SimAbstractMachine(private val clock: Clock) : SimMachine _usage.value = usage } - override fun close() { - if (!isTerminated) { - isTerminated = true - cpus.forEach(SimProcessingUnit::close) + /** + * Cancel the workload that is currently running on the machine. + */ + private fun cancel() { + interpreter.batch { + for (cpu in cpus) { + cpu.cancel() + } + } + + val cont = cont + if (cont != null) { + this.cont = null + cont.resume(Unit) } } + + /** + * The execution context in which the workload runs. + */ + private inner class Context(override val meta: Map<String, Any>) : SimMachineContext { + override val interpreter: SimResourceInterpreter + get() = this@SimAbstractMachine.interpreter + + override val cpus: List<SimProcessingUnit> = this@SimAbstractMachine.cpus + + override val memory: List<MemoryUnit> = model.memory + + override fun close() = cancel() + } } 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 index 27ebba21..5d5d1e5a 100644 --- 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 @@ -22,62 +22,36 @@ package org.opendc.simulator.compute -import kotlinx.coroutines.* -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.compute.power.PowerDriver import org.opendc.simulator.resources.* -import org.opendc.utils.TimerScheduler -import java.time.Clock -import kotlin.coroutines.* +import org.opendc.simulator.resources.SimResourceInterpreter /** * 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]. + * 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 interpreter The [SimResourceInterpreter] to drive the simulation. * @param model The machine model to simulate. + * @param powerDriver The power driver to use. + * @param parent The parent simulation system. */ -@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 = SimResourceSchedulerTrampoline(this.context, clock) - - override val cpus: List<SimProcessingUnit> = model.cpus.map { ProcessingUnitImpl(it) } + interpreter: SimResourceInterpreter, + model: SimMachineModel, + powerDriver: PowerDriver, + parent: SimResourceSystem? = null, +) : SimAbstractMachine(interpreter, parent, model) { + override val cpus: List<SimProcessingUnit> = model.cpus.map { cpu -> + Cpu(SimResourceSource(cpu.frequency, interpreter, this@SimBareMetalMachine), cpu) + } /** - * Construct the [ScalingDriver.Logic] for this machine. + * Construct the [PowerDriver.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() } - } + private val powerDriver = powerDriver.createLogic(this, cpus) /** * The power draw of the machine. @@ -88,45 +62,25 @@ public class SimBareMetalMachine( override fun updateUsage(usage: Double) { super.updateUsage(usage) - scalingGovernors.forEach { it.onLimit() } - powerDraw = scalingDriver.computePower() - } - - override fun close() { - super.close() - - scope.cancel() + powerDraw = powerDriver.computePower() } /** - * The [SimProcessingUnit] of this machine. + * A [SimProcessingUnit] of a bare-metal machine. */ - public inner class ProcessingUnitImpl(override val model: ProcessingUnit) : SimProcessingUnit { - /** - * The actual resource supporting the processing unit. - */ - private val source = SimResourceSource(model.frequency, 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() - } + private class Cpu( + private val source: SimResourceSource, + override val model: ProcessingUnit + ) : SimProcessingUnit, SimResourceProvider by source { + override var capacity: Double + get() = source.capacity + set(value) { + // Clamp the capacity of the CPU between [0.0, maxFreq] + if (value >= 0.0 && value <= model.frequency) { + source.capacity = value + } + } + + override fun toString(): String = "SimBareMetalMachine.Cpu[model=$model]" } } 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 index 11aec2de..e7776c81 100644 --- 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 @@ -22,35 +22,72 @@ package org.opendc.simulator.compute +import org.opendc.simulator.compute.cpufreq.ScalingGovernor import org.opendc.simulator.compute.workload.SimWorkload -import org.opendc.simulator.resources.* +import org.opendc.simulator.resources.SimResourceInterpreter +import org.opendc.simulator.resources.SimResourceSwitch +import org.opendc.simulator.resources.SimResourceSwitchMaxMin +import org.opendc.simulator.resources.SimResourceSystem /** * A [SimHypervisor] that distributes the computing requirements of multiple [SimWorkload] on a single * [SimBareMetalMachine] concurrently using weighted fair sharing. * + * @param interpreter The interpreter to manage the machine's resources. + * @param parent The parent simulation system. + * @param scalingGovernor The CPU frequency scaling governor to use for the hypervisor. * @param listener The hypervisor listener to use. */ -public class SimFairShareHypervisor(private val scheduler: SimResourceScheduler, private val listener: SimHypervisor.Listener? = null) : SimAbstractHypervisor() { +public class SimFairShareHypervisor( + private val interpreter: SimResourceInterpreter, + private val parent: SimResourceSystem? = null, + scalingGovernor: ScalingGovernor? = null, + private val listener: SimHypervisor.Listener? = null +) : SimAbstractHypervisor(interpreter, scalingGovernor) { override fun canFit(model: SimMachineModel, switch: SimResourceSwitch): Boolean = true override fun createSwitch(ctx: SimMachineContext): SimResourceSwitch { - return SimResourceSwitchMaxMin( - scheduler, - 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) - } + return SwitchSystem(ctx).switch + } + + private inner class SwitchSystem(private val ctx: SimMachineContext) : SimResourceSystem { + val switch = SimResourceSwitchMaxMin(interpreter, this) + + override val parent: SimResourceSystem? = this@SimFairShareHypervisor.parent + + private var lastCpuUsage = 0.0 + private var lastCpuDemand = 0.0 + private var lastDemand = 0.0 + private var lastActual = 0.0 + private var lastOvercommit = 0.0 + private var lastReport = Long.MIN_VALUE + + override fun onConverge(timestamp: Long) { + val listener = listener ?: return + val counters = switch.counters + + if (timestamp > lastReport) { + listener.onSliceFinish( + this@SimFairShareHypervisor, + (counters.demand - lastDemand).toLong(), + (counters.actual - lastActual).toLong(), + (counters.overcommit - lastOvercommit).toLong(), + 0L, + lastCpuUsage, + lastCpuDemand + ) } - ) + lastReport = timestamp + + lastCpuDemand = switch.inputs.sumOf { it.demand } + lastCpuUsage = switch.inputs.sumOf { it.speed } + lastDemand = counters.demand + lastActual = counters.actual + lastOvercommit = counters.overcommit + + val load = lastCpuDemand / ctx.cpus.sumOf { it.model.frequency } + triggerGovernors(load) + } } } 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 index 2ab3ea09..94c905b2 100644 --- 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 @@ -22,9 +22,8 @@ package org.opendc.simulator.compute -import org.opendc.simulator.resources.SimResourceSchedulerTrampoline -import java.time.Clock -import kotlin.coroutines.CoroutineContext +import org.opendc.simulator.resources.SimResourceInterpreter +import org.opendc.simulator.resources.SimResourceSystem /** * A [SimHypervisorProvider] for the [SimFairShareHypervisor] implementation. @@ -32,7 +31,9 @@ import kotlin.coroutines.CoroutineContext public class SimFairShareHypervisorProvider : SimHypervisorProvider { override val id: String = "fair-share" - override fun create(context: CoroutineContext, clock: Clock, listener: SimHypervisor.Listener?): SimHypervisor { - return SimFairShareHypervisor(SimResourceSchedulerTrampoline(context, clock), listener) - } + override fun create( + interpreter: SimResourceInterpreter, + parent: SimResourceSystem?, + listener: SimHypervisor.Listener? + ): SimHypervisor = SimFairShareHypervisor(interpreter, parent, listener = listener) } 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 index b66020f4..8e8c3698 100644 --- 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 @@ -22,8 +22,8 @@ package org.opendc.simulator.compute -import java.time.Clock -import kotlin.coroutines.CoroutineContext +import org.opendc.simulator.resources.SimResourceInterpreter +import org.opendc.simulator.resources.SimResourceSystem /** * A service provider interface for constructing a [SimHypervisor]. @@ -40,5 +40,9 @@ public interface SimHypervisorProvider { /** * Create a [SimHypervisor] instance with the specified [listener]. */ - public fun create(context: CoroutineContext, clock: Clock, listener: SimHypervisor.Listener? = null): SimHypervisor + public fun create( + interpreter: SimResourceInterpreter, + parent: SimResourceSystem? = null, + listener: SimHypervisor.Listener? = null + ): SimHypervisor } 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 index c2523a2a..5cbabc86 100644 --- 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 @@ -23,18 +23,18 @@ package org.opendc.simulator.compute import org.opendc.simulator.compute.model.MemoryUnit -import java.time.Clock +import org.opendc.simulator.resources.SimResourceInterpreter /** * 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 { +public interface SimMachineContext : AutoCloseable { /** - * The virtual clock tracking simulation time. + * The resource interpreter that simulates the machine. */ - public val clock: Clock + public val interpreter: SimResourceInterpreter /** * The metadata associated with the context. @@ -50,4 +50,9 @@ public interface SimMachineContext { * The memory available on the machine */ public val memory: List<MemoryUnit> + + /** + * Stop the workload. + */ + public override fun close() } 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 index 13c7d9b2..93c9ddfa 100644 --- 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 @@ -30,12 +30,12 @@ import org.opendc.simulator.resources.SimResourceProvider */ public interface SimProcessingUnit : SimResourceProvider { /** - * The model representing the static properties of the processing unit. + * The capacity of the processing unit, which can be adjusted by the workload if supported by the machine. */ - public val model: ProcessingUnit + public override var capacity: Double /** - * The current speed of the processing unit. + * The model representing the static properties of the processing unit. */ - public val speed: Double + public val model: ProcessingUnit } 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 index fd8e546f..f6ae18f7 100644 --- 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 @@ -22,12 +22,14 @@ package org.opendc.simulator.compute -import org.opendc.simulator.resources.* +import org.opendc.simulator.resources.SimResourceInterpreter +import org.opendc.simulator.resources.SimResourceSwitch +import org.opendc.simulator.resources.SimResourceSwitchExclusive /** * A [SimHypervisor] that allocates its sub-resources exclusively for the virtual machine that it hosts. */ -public class SimSpaceSharedHypervisor : SimAbstractHypervisor() { +public class SimSpaceSharedHypervisor(interpreter: SimResourceInterpreter) : SimAbstractHypervisor(interpreter, null) { override fun canFit(model: SimMachineModel, switch: SimResourceSwitch): Boolean { return switch.inputs.size - switch.outputs.size >= model.cpus.size } 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 index 83b924d7..923b5bab 100644 --- 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 @@ -22,8 +22,8 @@ package org.opendc.simulator.compute -import java.time.Clock -import kotlin.coroutines.CoroutineContext +import org.opendc.simulator.resources.SimResourceInterpreter +import org.opendc.simulator.resources.SimResourceSystem /** * A [SimHypervisorProvider] for the [SimSpaceSharedHypervisor] implementation. @@ -31,7 +31,9 @@ import kotlin.coroutines.CoroutineContext public class SimSpaceSharedHypervisorProvider : SimHypervisorProvider { override val id: String = "space-shared" - override fun create(context: CoroutineContext, clock: Clock, listener: SimHypervisor.Listener?): SimHypervisor { - return SimSpaceSharedHypervisor() - } + override fun create( + interpreter: SimResourceInterpreter, + parent: SimResourceSystem?, + listener: SimHypervisor.Listener? + ): SimHypervisor = SimSpaceSharedHypervisor(interpreter) } 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 deleted file mode 100644 index ddbe1ca0..00000000 --- a/opendc-simulator/opendc-simulator-compute/src/main/kotlin/org/opendc/simulator/compute/cpufreq/DemandScalingGovernor.kt +++ /dev/null @@ -1,36 +0,0 @@ -/* - * 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/PerformanceScalingGovernor.kt b/opendc-simulator/opendc-simulator-compute/src/main/kotlin/org/opendc/simulator/compute/cpufreq/PerformanceScalingGovernor.kt index 96f8775a..245877be 100644 --- 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 @@ -22,13 +22,15 @@ package org.opendc.simulator.compute.cpufreq +import org.opendc.simulator.compute.SimProcessingUnit + /** * 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 createLogic(cpu: SimProcessingUnit): ScalingGovernor.Logic = object : ScalingGovernor.Logic { + override fun onStart() { + cpu.capacity = 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/ScalingGovernor.kt b/opendc-simulator/opendc-simulator-compute/src/main/kotlin/org/opendc/simulator/compute/cpufreq/ScalingGovernor.kt index c9aea580..b7e7ffc6 100644 --- 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 @@ -22,6 +22,8 @@ package org.opendc.simulator.compute.cpufreq +import org.opendc.simulator.compute.SimProcessingUnit + /** * 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. @@ -33,9 +35,9 @@ package org.opendc.simulator.compute.cpufreq */ public interface ScalingGovernor { /** - * Create the scaling logic for the specified [context] + * Create the scaling logic for the specified [cpu] */ - public fun createLogic(ctx: ScalingContext): Logic + public fun createLogic(cpu: SimProcessingUnit): Logic /** * The logic of the scaling governor. @@ -48,7 +50,9 @@ public interface ScalingGovernor { /** * This method is invoked when the governor should re-decide the frequency limits. + * + * @param load The load of the system. */ - public fun onLimit() {} + public fun onLimit(load: Double) {} } } 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 deleted file mode 100644 index cf0bbb28..00000000 --- a/opendc-simulator/opendc-simulator-compute/src/main/kotlin/org/opendc/simulator/compute/cpufreq/SimpleScalingDriver.kt +++ /dev/null @@ -1,49 +0,0 @@ -/* - * 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/cpufreq/PStateScalingDriver.kt b/opendc-simulator/opendc-simulator-compute/src/main/kotlin/org/opendc/simulator/compute/power/PStatePowerDriver.kt index 6f44d778..6328c8e4 100644 --- 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/power/PStatePowerDriver.kt @@ -20,67 +20,41 @@ * SOFTWARE. */ -package org.opendc.simulator.compute.cpufreq +package org.opendc.simulator.compute.power 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. + * A [PowerDriver] that computes the power draw using multiple [PowerModel]s based on multiple frequency states. * * @param states A map describing the states of the driver. */ -public class PStateScalingDriver(states: Map<Double, PowerModel>) : ScalingDriver { +public class PStatePowerDriver(states: Map<Double, PowerModel>) : PowerDriver { /** * 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 createLogic(machine: SimMachine, cpus: List<SimProcessingUnit>): PowerDriver.Logic = object : PowerDriver.Logic { 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 + for (cpu in cpus) { + targetFreq = max(cpu.capacity, targetFreq) + totalSpeed += cpu.speed } val maxFreq = states.lastKey() val (actualFreq, model) = states.ceilingEntry(min(maxFreq, targetFreq)) - val utilization = totalSpeed / (actualFreq * contexts.size) + val utilization = totalSpeed / (actualFreq * cpus.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" + override fun toString(): String = "PStatePowerDriver.Logic" } } 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/power/PowerDriver.kt index b4fd7550..a1a2b911 100644 --- 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/power/PowerDriver.kt @@ -20,30 +20,25 @@ * SOFTWARE. */ -package org.opendc.simulator.compute.cpufreq +package org.opendc.simulator.compute.power import org.opendc.simulator.compute.SimMachine import org.opendc.simulator.compute.SimProcessingUnit /** - * A [ScalingDriver] is responsible for switching the processor to the correct frequency. + * A [PowerDriver] is responsible for switching the processor to the correct frequency. */ -public interface ScalingDriver { +public interface PowerDriver { /** * Create the scaling logic for the specified [machine] */ - public fun createLogic(machine: SimMachine): Logic + public fun createLogic(machine: SimMachine, cpus: List<SimProcessingUnit>): 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. 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/power/SimplePowerDriver.kt index 18338079..5c5ceff5 100644 --- 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/power/SimplePowerDriver.kt @@ -20,27 +20,20 @@ * SOFTWARE. */ -package org.opendc.simulator.compute.cpufreq +package org.opendc.simulator.compute.power 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. + * A [PowerDriver] that computes the power consumption based on a single specified [power model][model]. */ -public interface ScalingContext { - /** - * The machine the processing unit belongs to. - */ - public val machine: SimMachine +public class SimplePowerDriver(private val model: PowerModel) : PowerDriver { + override fun createLogic(machine: SimMachine, cpus: List<SimProcessingUnit>): PowerDriver.Logic = object : PowerDriver.Logic { + override fun computePower(): Double { + return model.computePower(machine.usage.value) + } - /** - * 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) + override fun toString(): String = "SimplePowerDriver.Logic" + } } diff --git a/opendc-simulator/opendc-simulator-compute/src/main/kotlin/org/opendc/simulator/compute/util/SimWorkloadLifecycle.kt b/opendc-simulator/opendc-simulator-compute/src/main/kotlin/org/opendc/simulator/compute/util/SimWorkloadLifecycle.kt new file mode 100644 index 00000000..43662d93 --- /dev/null +++ b/opendc-simulator/opendc-simulator-compute/src/main/kotlin/org/opendc/simulator/compute/util/SimWorkloadLifecycle.kt @@ -0,0 +1,76 @@ +/* + * 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.util + +import org.opendc.simulator.compute.SimMachineContext +import org.opendc.simulator.compute.workload.SimWorkload +import org.opendc.simulator.resources.SimResourceConsumer +import org.opendc.simulator.resources.SimResourceContext +import org.opendc.simulator.resources.SimResourceEvent + +/** + * A helper class to manage the lifecycle of a [SimWorkload] + */ +public class SimWorkloadLifecycle(private val ctx: SimMachineContext) { + /** + * The resource consumers which represent the lifecycle of the workload. + */ + private val waiting = mutableSetOf<SimResourceConsumer>() + + /** + * Wait for the specified [consumer] to complete before ending the lifecycle of the workload. + */ + public fun waitFor(consumer: SimResourceConsumer): SimResourceConsumer { + waiting.add(consumer) + return object : SimResourceConsumer by consumer { + override fun onEvent(ctx: SimResourceContext, event: SimResourceEvent) { + try { + consumer.onEvent(ctx, event) + } finally { + if (event == SimResourceEvent.Exit) { + complete(consumer) + } + } + } + + override fun onFailure(ctx: SimResourceContext, cause: Throwable) { + try { + consumer.onFailure(ctx, cause) + } finally { + complete(consumer) + } + } + + override fun toString(): String = "SimWorkloadLifecycle.Consumer[delegate=$consumer]" + } + } + + /** + * Complete the specified [SimResourceConsumer]. + */ + private fun complete(consumer: SimResourceConsumer) { + if (waiting.remove(consumer) && waiting.isEmpty()) { + ctx.close() + } + } +} 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 index 63c9d28c..de6832ca 100644 --- 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 @@ -23,8 +23,7 @@ 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.compute.util.SimWorkloadLifecycle import org.opendc.simulator.resources.consumer.SimWorkConsumer /** @@ -43,10 +42,11 @@ public class SimFlopsWorkload( 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 onStart(ctx: SimMachineContext) { + val lifecycle = SimWorkloadLifecycle(ctx) + for (cpu in ctx.cpus) { + cpu.startConsumer(lifecycle.waitFor(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 index a3420e32..318a6b49 100644 --- 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 @@ -23,8 +23,7 @@ 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.compute.util.SimWorkloadLifecycle import org.opendc.simulator.resources.consumer.SimWorkConsumer /** @@ -42,11 +41,12 @@ public class SimRuntimeWorkload( 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 onStart(ctx: SimMachineContext) { + val lifecycle = SimWorkloadLifecycle(ctx) + for (cpu in ctx.cpus) { + val limit = cpu.capacity * utilization + cpu.startConsumer(lifecycle.waitFor(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 index ffb332d1..6929f4d2 100644 --- 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 @@ -24,6 +24,7 @@ package org.opendc.simulator.compute.workload import org.opendc.simulator.compute.SimMachineContext import org.opendc.simulator.compute.model.ProcessingUnit +import org.opendc.simulator.compute.util.SimWorkloadLifecycle import org.opendc.simulator.resources.SimResourceCommand import org.opendc.simulator.resources.SimResourceConsumer import org.opendc.simulator.resources.SimResourceContext @@ -44,33 +45,12 @@ public class SimTraceWorkload(public val trace: Sequence<Fragment>) : SimWorkloa 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) + offset = ctx.interpreter.clock.millis() - if (barrier.enter()) { - this@SimTraceWorkload.fragment = nextFragment() - this@SimTraceWorkload.offset += fragment.duration - } + val lifecycle = SimWorkloadLifecycle(ctx) - return cmd - } + for (cpu in ctx.cpus) { + cpu.startConsumer(lifecycle.waitFor(Consumer(cpu.model))) } } @@ -87,6 +67,31 @@ public class SimTraceWorkload(public val trace: Sequence<Fragment>) : SimWorkloa } } + private inner class Consumer(val cpu: ProcessingUnit) : 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 + } + } + /** * A fragment of the workload. */ 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 index bdc12bb5..b80665fa 100644 --- 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 @@ -23,8 +23,6 @@ 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. @@ -35,11 +33,8 @@ import org.opendc.simulator.resources.SimResourceConsumer public interface SimWorkload { /** * This method is invoked when the workload is started. + * + * @param ctx The execution context in which the machine runs. */ 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 index 8886caa7..b15692ec 100644 --- 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 @@ -31,15 +31,16 @@ 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.junit.jupiter.api.assertDoesNotThrow 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.power.SimplePowerDriver import org.opendc.simulator.compute.workload.SimTraceWorkload import org.opendc.simulator.core.runBlockingSimulation -import org.opendc.simulator.resources.SimResourceSchedulerTrampoline +import org.opendc.simulator.resources.SimResourceInterpreter /** * Test suite for the [SimHypervisor] class. @@ -93,8 +94,9 @@ internal class SimHypervisorTest { ), ) - val machine = SimBareMetalMachine(coroutineContext, clock, model, PerformanceScalingGovernor(), SimpleScalingDriver(ConstantPowerModel(0.0))) - val hypervisor = SimFairShareHypervisor(SimResourceSchedulerTrampoline(coroutineContext, clock), listener) + val platform = SimResourceInterpreter(coroutineContext, clock) + val machine = SimBareMetalMachine(platform, model, SimplePowerDriver(ConstantPowerModel(0.0))) + val hypervisor = SimFairShareHypervisor(platform, scalingGovernor = PerformanceScalingGovernor(), listener = listener) launch { machine.run(hypervisor) @@ -164,11 +166,11 @@ internal class SimHypervisorTest { ) ) + val platform = SimResourceInterpreter(coroutineContext, clock) val machine = SimBareMetalMachine( - coroutineContext, clock, model, PerformanceScalingGovernor(), - SimpleScalingDriver(ConstantPowerModel(0.0)) + platform, model, SimplePowerDriver(ConstantPowerModel(0.0)) ) - val hypervisor = SimFairShareHypervisor(SimResourceSchedulerTrampoline(coroutineContext, clock), listener) + val hypervisor = SimFairShareHypervisor(platform, listener = listener) launch { machine.run(hypervisor) @@ -190,10 +192,33 @@ internal class SimHypervisorTest { yield() assertAll( - { assertEquals(2082000, listener.totalRequestedWork, "Requested Burst does not match") }, - { assertEquals(1062000, listener.totalGrantedWork, "Granted Burst does not match") }, + { assertEquals(2073600, listener.totalRequestedWork, "Requested Burst does not match") }, + { assertEquals(1053600, listener.totalGrantedWork, "Granted Burst does not match") }, { assertEquals(1020000, listener.totalOvercommittedWork, "Overcommissioned Burst does not match") }, { assertEquals(1200000, clock.millis()) } ) } + + @Test + fun testMultipleCPUs() = runBlockingSimulation { + val cpuNode = ProcessingNode("Intel", "Xeon", "amd64", 2) + val model = SimMachineModel( + cpus = List(cpuNode.coreCount) { ProcessingUnit(cpuNode, it, 3200.0) }, + memory = List(4) { MemoryUnit("Crucial", "MTA18ASF4G72AZ-3G2B1", 3200.0, 32_000) } + ) + + val platform = SimResourceInterpreter(coroutineContext, clock) + val machine = SimBareMetalMachine( + platform, model, SimplePowerDriver(ConstantPowerModel(0.0)) + ) + val hypervisor = SimFairShareHypervisor(platform) + + assertDoesNotThrow { + launch { + machine.run(hypervisor) + } + } + + machine.close() + } } 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 index 205f2eca..69f562d2 100644 --- 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 @@ -29,14 +29,14 @@ 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.power.SimplePowerDriver import org.opendc.simulator.compute.workload.SimFlopsWorkload import org.opendc.simulator.core.runBlockingSimulation +import org.opendc.simulator.resources.SimResourceInterpreter /** * Test suite for the [SimBareMetalMachine] class. @@ -57,7 +57,11 @@ class SimMachineTest { @Test fun testFlopsWorkload() = runBlockingSimulation { - val machine = SimBareMetalMachine(coroutineContext, clock, machineModel, PerformanceScalingGovernor(), SimpleScalingDriver(ConstantPowerModel(0.0))) + val machine = SimBareMetalMachine( + SimResourceInterpreter(coroutineContext, clock), + machineModel, + SimplePowerDriver(ConstantPowerModel(0.0)) + ) try { machine.run(SimFlopsWorkload(2_000, utilization = 1.0)) @@ -76,7 +80,11 @@ class SimMachineTest { 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))) + val machine = SimBareMetalMachine( + SimResourceInterpreter(coroutineContext, clock), + machineModel, + SimplePowerDriver(ConstantPowerModel(0.0)) + ) try { machine.run(SimFlopsWorkload(2_000, utilization = 1.0)) @@ -90,7 +98,11 @@ class SimMachineTest { @Test fun testUsage() = runBlockingSimulation { - val machine = SimBareMetalMachine(coroutineContext, clock, machineModel, PerformanceScalingGovernor(), SimpleScalingDriver(ConstantPowerModel(0.0))) + val machine = SimBareMetalMachine( + SimResourceInterpreter(coroutineContext, clock), + machineModel, + SimplePowerDriver(ConstantPowerModel(0.0)) + ) val res = mutableListOf<Double>() val job = launch { machine.usage.toList(res) } @@ -99,7 +111,7 @@ class SimMachineTest { 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" } + assertEquals(listOf(0.0, 1.0, 0.0), res) { "Machine is fully utilized" } } finally { machine.close() } @@ -107,7 +119,11 @@ class SimMachineTest { @Test fun testClose() = runBlockingSimulation { - val machine = SimBareMetalMachine(coroutineContext, clock, machineModel, PerformanceScalingGovernor(), SimpleScalingDriver(ConstantPowerModel(0.0))) + val machine = SimBareMetalMachine( + SimResourceInterpreter(coroutineContext, clock), + machineModel, + SimplePowerDriver(ConstantPowerModel(0.0)) + ) machine.close() assertDoesNotThrow { machine.close() } 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 index ef6f536d..dba3e9a1 100644 --- 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 @@ -30,16 +30,16 @@ 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.power.SimplePowerDriver 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 +import org.opendc.simulator.resources.SimResourceInterpreter /** * A test suite for the [SimSpaceSharedHypervisor]. @@ -76,11 +76,11 @@ internal class SimSpaceSharedHypervisorTest { ), ) + val interpreter = SimResourceInterpreter(coroutineContext, clock) val machine = SimBareMetalMachine( - coroutineContext, clock, machineModel, PerformanceScalingGovernor(), - SimpleScalingDriver(ConstantPowerModel(0.0)) + SimResourceInterpreter(coroutineContext, clock), machineModel, SimplePowerDriver(ConstantPowerModel(0.0)) ) - val hypervisor = SimSpaceSharedHypervisor() + val hypervisor = SimSpaceSharedHypervisor(interpreter) val colA = launch { machine.usage.toList(usagePm) } launch { machine.run(hypervisor) } @@ -112,11 +112,11 @@ internal class SimSpaceSharedHypervisorTest { fun testRuntimeWorkload() = runBlockingSimulation { val duration = 5 * 60L * 1000 val workload = SimRuntimeWorkload(duration) + val interpreter = SimResourceInterpreter(coroutineContext, clock) val machine = SimBareMetalMachine( - coroutineContext, clock, machineModel, PerformanceScalingGovernor(), - SimpleScalingDriver(ConstantPowerModel(0.0)) + interpreter, machineModel, SimplePowerDriver(ConstantPowerModel(0.0)) ) - val hypervisor = SimSpaceSharedHypervisor() + val hypervisor = SimSpaceSharedHypervisor(interpreter) launch { machine.run(hypervisor) } yield() @@ -135,11 +135,11 @@ internal class SimSpaceSharedHypervisorTest { fun testFlopsWorkload() = runBlockingSimulation { val duration = 5 * 60L * 1000 val workload = SimFlopsWorkload((duration * 3.2).toLong(), 1.0) + val interpreter = SimResourceInterpreter(coroutineContext, clock) val machine = SimBareMetalMachine( - coroutineContext, clock, machineModel, PerformanceScalingGovernor(), - SimpleScalingDriver(ConstantPowerModel(0.0)) + interpreter, machineModel, SimplePowerDriver(ConstantPowerModel(0.0)) ) - val hypervisor = SimSpaceSharedHypervisor() + val hypervisor = SimSpaceSharedHypervisor(interpreter) launch { machine.run(hypervisor) } yield() @@ -156,11 +156,11 @@ internal class SimSpaceSharedHypervisorTest { @Test fun testTwoWorkloads() = runBlockingSimulation { val duration = 5 * 60L * 1000 + val interpreter = SimResourceInterpreter(coroutineContext, clock) val machine = SimBareMetalMachine( - coroutineContext, clock, machineModel, PerformanceScalingGovernor(), - SimpleScalingDriver(ConstantPowerModel(0.0)) + interpreter, machineModel, SimplePowerDriver(ConstantPowerModel(0.0)) ) - val hypervisor = SimSpaceSharedHypervisor() + val hypervisor = SimSpaceSharedHypervisor(interpreter) launch { machine.run(hypervisor) } yield() @@ -182,11 +182,11 @@ internal class SimSpaceSharedHypervisorTest { */ @Test fun testConcurrentWorkloadFails() = runBlockingSimulation { + val interpreter = SimResourceInterpreter(coroutineContext, clock) val machine = SimBareMetalMachine( - coroutineContext, clock, machineModel, PerformanceScalingGovernor(), - SimpleScalingDriver(ConstantPowerModel(0.0)) + interpreter, machineModel, SimplePowerDriver(ConstantPowerModel(0.0)) ) - val hypervisor = SimSpaceSharedHypervisor() + val hypervisor = SimSpaceSharedHypervisor(interpreter) launch { machine.run(hypervisor) } yield() @@ -206,11 +206,11 @@ internal class SimSpaceSharedHypervisorTest { */ @Test fun testConcurrentWorkloadSucceeds() = runBlockingSimulation { + val interpreter = SimResourceInterpreter(coroutineContext, clock) val machine = SimBareMetalMachine( - coroutineContext, clock, machineModel, PerformanceScalingGovernor(), - SimpleScalingDriver(ConstantPowerModel(0.0)) + interpreter, machineModel, SimplePowerDriver(ConstantPowerModel(0.0)) ) - val hypervisor = SimSpaceSharedHypervisor() + val hypervisor = SimSpaceSharedHypervisor(interpreter) launch { machine.run(hypervisor) } yield() 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/PerformanceScalingGovernorTest.kt index c482d348..8e8b09c8 100644 --- 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/PerformanceScalingGovernorTest.kt @@ -26,23 +26,24 @@ import io.mockk.every import io.mockk.mockk import io.mockk.verify import org.junit.jupiter.api.Test +import org.opendc.simulator.compute.SimProcessingUnit /** - * Test suite for the [DemandScalingGovernor] + * Test suite for the [PerformanceScalingGovernor] */ -internal class DemandScalingGovernorTest { +internal class PerformanceScalingGovernorTest { @Test - fun testSetDemandLimit() { - val ctx = mockk<ScalingContext>(relaxUnitFun = true) + fun testSetStartLimit() { + val cpu = mockk<SimProcessingUnit>(relaxUnitFun = true) - every { ctx.cpu.speed } returns 2100.0 + every { cpu.model.frequency } returns 4100.0 + every { cpu.speed } returns 2100.0 - val logic = DemandScalingGovernor().createLogic(ctx) + val logic = PerformanceScalingGovernor().createLogic(cpu) logic.onStart() - verify(exactly = 0) { ctx.setTarget(any()) } + logic.onLimit(1.0) - logic.onLimit() - verify(exactly = 1) { ctx.setTarget(2100.0) } + verify(exactly = 1) { cpu.capacity = 4100.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/power/PStatePowerDriverTest.kt index bbea3ee2..35fd7c4c 100644 --- 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/power/PStatePowerDriverTest.kt @@ -20,7 +20,7 @@ * SOFTWARE. */ -package org.opendc.simulator.compute.cpufreq +package org.opendc.simulator.compute.power import io.mockk.every import io.mockk.mockk @@ -28,18 +28,16 @@ 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]. + * Test suite for [PStatePowerDriver]. */ -internal class PStateScalingDriverTest { +internal class PStatePowerDriverTest { @Test - fun testPowerWithoutGovernor() { + fun testPowerBaseline() { val machine = mockk<SimBareMetalMachine>() - val driver = PStateScalingDriver( + val driver = PStatePowerDriver( sortedMapOf( 2800.0 to ConstantPowerModel(200.0), 3300.0 to ConstantPowerModel(300.0), @@ -47,19 +45,19 @@ internal class PStateScalingDriverTest { ) ) - val logic = driver.createLogic(machine) + val logic = driver.createLogic(machine, emptyList()) assertEquals(200.0, logic.computePower()) } @Test - fun testPowerWithSingleGovernor() { + fun testPowerWithSingleCpu() { val machine = mockk<SimBareMetalMachine>() val cpu = mockk<SimProcessingUnit>() - every { cpu.model.frequency } returns 4100.0 + every { cpu.capacity } returns 3200.0 every { cpu.speed } returns 1200.0 - val driver = PStateScalingDriver( + val driver = PStatePowerDriver( sortedMapOf( 2800.0 to ConstantPowerModel(200.0), 3300.0 to ConstantPowerModel(300.0), @@ -67,23 +65,26 @@ internal class PStateScalingDriverTest { ) ) - val logic = driver.createLogic(machine) - - val scalingContext = logic.createContext(cpu) - scalingContext.setTarget(3200.0) + val logic = driver.createLogic(machine, listOf(cpu)) assertEquals(300.0, logic.computePower()) } @Test - fun testPowerWithMultipleGovernors() { + fun testPowerWithMultipleCpus() { val machine = mockk<SimBareMetalMachine>() - val cpu = mockk<SimProcessingUnit>() + val cpus = listOf( + mockk<SimProcessingUnit>(), + mockk() + ) - every { cpu.model.frequency } returns 4100.0 - every { cpu.speed } returns 1200.0 + every { cpus[0].capacity } returns 1000.0 + every { cpus[0].speed } returns 1200.0 - val driver = PStateScalingDriver( + every { cpus[1].capacity } returns 3500.0 + every { cpus[1].speed } returns 1200.0 + + val driver = PStatePowerDriver( sortedMapOf( 2800.0 to ConstantPowerModel(200.0), 3300.0 to ConstantPowerModel(300.0), @@ -91,13 +92,7 @@ internal class PStateScalingDriverTest { ) ) - val logic = driver.createLogic(machine) - - val scalingContextA = logic.createContext(cpu) - scalingContextA.setTarget(1000.0) - - val scalingContextB = logic.createContext(cpu) - scalingContextB.setTarget(3400.0) + val logic = driver.createLogic(machine, cpus) assertEquals(350.0, logic.computePower()) } @@ -109,7 +104,7 @@ internal class PStateScalingDriverTest { every { cpu.model.frequency } returns 4200.0 - val driver = PStateScalingDriver( + val driver = PStatePowerDriver( sortedMapOf( 2800.0 to LinearPowerModel(200.0, 100.0), 3300.0 to LinearPowerModel(250.0, 150.0), @@ -117,16 +112,14 @@ internal class PStateScalingDriverTest { ) ) - val logic = driver.createLogic(machine) - - val scalingContext = logic.createContext(cpu) + val logic = driver.createLogic(machine, listOf(cpu)) every { cpu.speed } returns 1400.0 - scalingContext.setTarget(1400.0) + every { cpu.capacity } returns 1400.0 assertEquals(150.0, logic.computePower()) every { cpu.speed } returns 1400.0 - scalingContext.setTarget(4000.0) + every { cpu.capacity } returns 4000.0 assertEquals(235.0, logic.computePower()) } } |
