summaryrefslogtreecommitdiff
path: root/opendc-simulator
diff options
context:
space:
mode:
Diffstat (limited to 'opendc-simulator')
-rw-r--r--opendc-simulator/opendc-simulator-compute/build.gradle.kts7
-rw-r--r--opendc-simulator/opendc-simulator-compute/src/jmh/kotlin/org/opendc/simulator/compute/SimMachineBenchmarks.kt92
-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.kt196
-rw-r--r--opendc-simulator/opendc-simulator-compute/src/main/kotlin/org/opendc/simulator/compute/SimBareMetalMachine.kt136
-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/SimMachine.kt11
-rw-r--r--opendc-simulator/opendc-simulator-compute/src/main/kotlin/org/opendc/simulator/compute/SimMachineContext.kt28
-rw-r--r--opendc-simulator/opendc-simulator-compute/src/main/kotlin/org/opendc/simulator/compute/SimMemory.kt (renamed from opendc-simulator/opendc-simulator-compute/src/main/kotlin/org/opendc/simulator/compute/SimSpaceSharedHypervisorProvider.kt)17
-rw-r--r--opendc-simulator/opendc-simulator-compute/src/main/kotlin/org/opendc/simulator/compute/SimNetworkInterface.kt51
-rw-r--r--opendc-simulator/opendc-simulator-compute/src/main/kotlin/org/opendc/simulator/compute/SimProcessingUnit.kt12
-rw-r--r--opendc-simulator/opendc-simulator-compute/src/main/kotlin/org/opendc/simulator/compute/SimStorageInterface.kt (renamed from opendc-simulator/opendc-simulator-resources/src/main/kotlin/org/opendc/simulator/resources/SimResourceEvent.kt)24
-rw-r--r--opendc-simulator/opendc-simulator-compute/src/main/kotlin/org/opendc/simulator/compute/device/SimNetworkAdapter.kt (renamed from opendc-simulator/opendc-simulator-resources/src/main/kotlin/org/opendc/simulator/resources/SimResourceState.kt)23
-rw-r--r--opendc-simulator/opendc-simulator-compute/src/main/kotlin/org/opendc/simulator/compute/device/SimPeripheral.kt33
-rw-r--r--opendc-simulator/opendc-simulator-compute/src/main/kotlin/org/opendc/simulator/compute/device/SimPsu.kt114
-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/kernel/SimAbstractHypervisor.kt290
-rw-r--r--opendc-simulator/opendc-simulator-compute/src/main/kotlin/org/opendc/simulator/compute/kernel/SimFairShareHypervisor.kt56
-rw-r--r--opendc-simulator/opendc-simulator-compute/src/main/kotlin/org/opendc/simulator/compute/kernel/SimFairShareHypervisorProvider.kt (renamed from opendc-simulator/opendc-simulator-compute/src/main/kotlin/org/opendc/simulator/compute/SimFairShareHypervisorProvider.kt)18
-rw-r--r--opendc-simulator/opendc-simulator-compute/src/main/kotlin/org/opendc/simulator/compute/kernel/SimHypervisor.kt (renamed from opendc-simulator/opendc-simulator-compute/src/main/kotlin/org/opendc/simulator/compute/SimHypervisor.kt)56
-rw-r--r--opendc-simulator/opendc-simulator-compute/src/main/kotlin/org/opendc/simulator/compute/kernel/SimHypervisorCounters.kt (renamed from opendc-simulator/opendc-simulator-resources/src/main/kotlin/org/opendc/simulator/resources/SimResourceSwitch.kt)22
-rw-r--r--opendc-simulator/opendc-simulator-compute/src/main/kotlin/org/opendc/simulator/compute/kernel/SimHypervisorProvider.kt (renamed from opendc-simulator/opendc-simulator-compute/src/main/kotlin/org/opendc/simulator/compute/SimHypervisorProvider.kt)17
-rw-r--r--opendc-simulator/opendc-simulator-compute/src/main/kotlin/org/opendc/simulator/compute/kernel/SimSpaceSharedHypervisor.kt45
-rw-r--r--opendc-simulator/opendc-simulator-compute/src/main/kotlin/org/opendc/simulator/compute/kernel/SimSpaceSharedHypervisorProvider.kt42
-rw-r--r--opendc-simulator/opendc-simulator-compute/src/main/kotlin/org/opendc/simulator/compute/kernel/SimVirtualMachine.kt (renamed from opendc-simulator/opendc-simulator-compute/src/main/kotlin/org/opendc/simulator/compute/cpufreq/ScalingContext.kt)24
-rw-r--r--opendc-simulator/opendc-simulator-compute/src/main/kotlin/org/opendc/simulator/compute/kernel/cpufreq/ConservativeScalingGovernor.kt66
-rw-r--r--opendc-simulator/opendc-simulator-compute/src/main/kotlin/org/opendc/simulator/compute/kernel/cpufreq/OnDemandScalingGovernor.kt50
-rw-r--r--opendc-simulator/opendc-simulator-compute/src/main/kotlin/org/opendc/simulator/compute/kernel/cpufreq/PerformanceScalingGovernor.kt (renamed from opendc-simulator/opendc-simulator-compute/src/main/kotlin/org/opendc/simulator/compute/cpufreq/PerformanceScalingGovernor.kt)12
-rw-r--r--opendc-simulator/opendc-simulator-compute/src/main/kotlin/org/opendc/simulator/compute/kernel/cpufreq/PowerSaveScalingGovernor.kt (renamed from opendc-simulator/opendc-simulator-compute/src/main/kotlin/org/opendc/simulator/compute/cpufreq/DemandScalingGovernor.kt)16
-rw-r--r--opendc-simulator/opendc-simulator-compute/src/main/kotlin/org/opendc/simulator/compute/kernel/cpufreq/ScalingGovernor.kt (renamed from opendc-simulator/opendc-simulator-compute/src/main/kotlin/org/opendc/simulator/compute/cpufreq/ScalingGovernor.kt)10
-rw-r--r--opendc-simulator/opendc-simulator-compute/src/main/kotlin/org/opendc/simulator/compute/kernel/cpufreq/ScalingPolicy.kt51
-rw-r--r--opendc-simulator/opendc-simulator-compute/src/main/kotlin/org/opendc/simulator/compute/kernel/interference/VmInterferenceDomain.kt (renamed from opendc-simulator/opendc-simulator-compute/src/main/kotlin/org/opendc/simulator/compute/SimSpaceSharedHypervisor.kt)25
-rw-r--r--opendc-simulator/opendc-simulator-compute/src/main/kotlin/org/opendc/simulator/compute/kernel/interference/VmInterferenceGroup.kt (renamed from opendc-simulator/opendc-simulator-resources/src/main/kotlin/org/opendc/simulator/resources/SimResourceDistributor.kt)21
-rw-r--r--opendc-simulator/opendc-simulator-compute/src/main/kotlin/org/opendc/simulator/compute/kernel/interference/VmInterferenceModel.kt170
-rw-r--r--opendc-simulator/opendc-simulator-compute/src/main/kotlin/org/opendc/simulator/compute/model/MachineModel.kt (renamed from opendc-simulator/opendc-simulator-compute/src/main/kotlin/org/opendc/simulator/compute/SimMachineModel.kt)14
-rw-r--r--opendc-simulator/opendc-simulator-compute/src/main/kotlin/org/opendc/simulator/compute/model/NetworkAdapter.kt36
-rw-r--r--opendc-simulator/opendc-simulator-compute/src/main/kotlin/org/opendc/simulator/compute/model/StorageDevice.kt40
-rw-r--r--opendc-simulator/opendc-simulator-compute/src/main/kotlin/org/opendc/simulator/compute/power/AsymptoticPowerModel.kt22
-rw-r--r--opendc-simulator/opendc-simulator-compute/src/main/kotlin/org/opendc/simulator/compute/power/ConstantPowerModel.kt22
-rw-r--r--opendc-simulator/opendc-simulator-compute/src/main/kotlin/org/opendc/simulator/compute/power/CubicPowerModel.kt22
-rw-r--r--opendc-simulator/opendc-simulator-compute/src/main/kotlin/org/opendc/simulator/compute/power/InterpolationPowerModel.kt35
-rw-r--r--opendc-simulator/opendc-simulator-compute/src/main/kotlin/org/opendc/simulator/compute/power/LinearPowerModel.kt22
-rw-r--r--opendc-simulator/opendc-simulator-compute/src/main/kotlin/org/opendc/simulator/compute/power/MsePowerModel.kt22
-rw-r--r--opendc-simulator/opendc-simulator-compute/src/main/kotlin/org/opendc/simulator/compute/power/PStatePowerDriver.kt (renamed from opendc-simulator/opendc-simulator-compute/src/main/kotlin/org/opendc/simulator/compute/cpufreq/PStateScalingDriver.kt)46
-rw-r--r--opendc-simulator/opendc-simulator-compute/src/main/kotlin/org/opendc/simulator/compute/power/PowerDriver.kt (renamed from opendc-simulator/opendc-simulator-compute/src/main/kotlin/org/opendc/simulator/compute/cpufreq/ScalingDriver.kt)21
-rw-r--r--opendc-simulator/opendc-simulator-compute/src/main/kotlin/org/opendc/simulator/compute/power/PowerModel.kt22
-rw-r--r--opendc-simulator/opendc-simulator-compute/src/main/kotlin/org/opendc/simulator/compute/power/SimplePowerDriver.kt (renamed from opendc-simulator/opendc-simulator-compute/src/main/kotlin/org/opendc/simulator/compute/cpufreq/SimpleScalingDriver.kt)29
-rw-r--r--opendc-simulator/opendc-simulator-compute/src/main/kotlin/org/opendc/simulator/compute/power/SqrtPowerModel.kt22
-rw-r--r--opendc-simulator/opendc-simulator-compute/src/main/kotlin/org/opendc/simulator/compute/power/SquarePowerModel.kt22
-rw-r--r--opendc-simulator/opendc-simulator-compute/src/main/kotlin/org/opendc/simulator/compute/power/ZeroIdlePowerDecorator.kt22
-rw-r--r--opendc-simulator/opendc-simulator-compute/src/main/kotlin/org/opendc/simulator/compute/workload/SimFlopsWorkload.kt13
-rw-r--r--opendc-simulator/opendc-simulator-compute/src/main/kotlin/org/opendc/simulator/compute/workload/SimRuntimeWorkload.kt15
-rw-r--r--opendc-simulator/opendc-simulator-compute/src/main/kotlin/org/opendc/simulator/compute/workload/SimTrace.kt233
-rw-r--r--opendc-simulator/opendc-simulator-compute/src/main/kotlin/org/opendc/simulator/compute/workload/SimTraceFragment.kt38
-rw-r--r--opendc-simulator/opendc-simulator-compute/src/main/kotlin/org/opendc/simulator/compute/workload/SimTraceWorkload.kt64
-rw-r--r--opendc-simulator/opendc-simulator-compute/src/main/kotlin/org/opendc/simulator/compute/workload/SimWorkload.kt9
-rw-r--r--opendc-simulator/opendc-simulator-compute/src/main/kotlin/org/opendc/simulator/compute/workload/SimWorkloadLifecycle.kt63
-rw-r--r--opendc-simulator/opendc-simulator-compute/src/test/kotlin/org/opendc/simulator/compute/SimHypervisorTest.kt199
-rw-r--r--opendc-simulator/opendc-simulator-compute/src/test/kotlin/org/opendc/simulator/compute/SimMachineTest.kt261
-rw-r--r--opendc-simulator/opendc-simulator-compute/src/test/kotlin/org/opendc/simulator/compute/device/SimPsuTest.kt102
-rw-r--r--opendc-simulator/opendc-simulator-compute/src/test/kotlin/org/opendc/simulator/compute/kernel/SimFairShareHypervisorTest.kt240
-rw-r--r--opendc-simulator/opendc-simulator-compute/src/test/kotlin/org/opendc/simulator/compute/kernel/SimSpaceSharedHypervisorTest.kt (renamed from opendc-simulator/opendc-simulator-compute/src/test/kotlin/org/opendc/simulator/compute/SimSpaceSharedHypervisorTest.kt)88
-rw-r--r--opendc-simulator/opendc-simulator-compute/src/test/kotlin/org/opendc/simulator/compute/kernel/cpufreq/ConservativeScalingGovernorTest.kt98
-rw-r--r--opendc-simulator/opendc-simulator-compute/src/test/kotlin/org/opendc/simulator/compute/kernel/cpufreq/OnDemandScalingGovernorTest.kt81
-rw-r--r--opendc-simulator/opendc-simulator-compute/src/test/kotlin/org/opendc/simulator/compute/kernel/cpufreq/PerformanceScalingGovernorTest.kt (renamed from opendc-simulator/opendc-simulator-compute/src/test/kotlin/org/opendc/simulator/compute/cpufreq/DemandScalingGovernorTest.kt)26
-rw-r--r--opendc-simulator/opendc-simulator-compute/src/test/kotlin/org/opendc/simulator/compute/kernel/cpufreq/PowerSaveScalingGovernorTest.kt72
-rw-r--r--opendc-simulator/opendc-simulator-compute/src/test/kotlin/org/opendc/simulator/compute/power/PStatePowerDriverTest.kt (renamed from opendc-simulator/opendc-simulator-compute/src/test/kotlin/org/opendc/simulator/compute/cpufreq/PStateScalingDriverTest.kt)67
-rw-r--r--opendc-simulator/opendc-simulator-compute/src/test/kotlin/org/opendc/simulator/compute/power/PowerModelTest.kt25
-rw-r--r--opendc-simulator/opendc-simulator-compute/src/test/kotlin/org/opendc/simulator/compute/workload/SimTraceWorkloadTest.kt160
-rw-r--r--opendc-simulator/opendc-simulator-core/src/main/kotlin/org/opendc/simulator/core/SimulationCoroutineDispatcher.kt44
-rw-r--r--opendc-simulator/opendc-simulator-failures/src/main/kotlin/org/opendc/simulator/failures/CorrelatedFaultInjector.kt129
-rw-r--r--opendc-simulator/opendc-simulator-failures/src/main/kotlin/org/opendc/simulator/failures/FailureDomain.kt47
-rw-r--r--opendc-simulator/opendc-simulator-failures/src/main/kotlin/org/opendc/simulator/failures/UncorrelatedFaultInjector.kt61
-rw-r--r--opendc-simulator/opendc-simulator-flow/build.gradle.kts (renamed from opendc-simulator/opendc-simulator-resources/build.gradle.kts)6
-rw-r--r--opendc-simulator/opendc-simulator-flow/src/jmh/kotlin/org/opendc/simulator/flow/FlowBenchmarks.kt133
-rw-r--r--opendc-simulator/opendc-simulator-flow/src/main/kotlin/org/opendc/simulator/flow/FlowConnection.kt72
-rw-r--r--opendc-simulator/opendc-simulator-flow/src/main/kotlin/org/opendc/simulator/flow/FlowConsumer.kt131
-rw-r--r--opendc-simulator/opendc-simulator-flow/src/main/kotlin/org/opendc/simulator/flow/FlowConsumerContext.kt62
-rw-r--r--opendc-simulator/opendc-simulator-flow/src/main/kotlin/org/opendc/simulator/flow/FlowConsumerLogic.kt60
-rw-r--r--opendc-simulator/opendc-simulator-flow/src/main/kotlin/org/opendc/simulator/flow/FlowConvergenceListener.kt36
-rw-r--r--opendc-simulator/opendc-simulator-flow/src/main/kotlin/org/opendc/simulator/flow/FlowCounters.kt (renamed from opendc-simulator/opendc-simulator-resources/src/main/kotlin/org/opendc/simulator/resources/SimResourceAggregator.kt)27
-rw-r--r--opendc-simulator/opendc-simulator-flow/src/main/kotlin/org/opendc/simulator/flow/FlowEngine.kt95
-rw-r--r--opendc-simulator/opendc-simulator-flow/src/main/kotlin/org/opendc/simulator/flow/FlowForwarder.kt256
-rw-r--r--opendc-simulator/opendc-simulator-flow/src/main/kotlin/org/opendc/simulator/flow/FlowMapper.kt75
-rw-r--r--opendc-simulator/opendc-simulator-flow/src/main/kotlin/org/opendc/simulator/flow/FlowSink.kt155
-rw-r--r--opendc-simulator/opendc-simulator-flow/src/main/kotlin/org/opendc/simulator/flow/FlowSource.kt70
-rw-r--r--opendc-simulator/opendc-simulator-flow/src/main/kotlin/org/opendc/simulator/flow/interference/InterferenceDomain.kt19
-rw-r--r--opendc-simulator/opendc-simulator-flow/src/main/kotlin/org/opendc/simulator/flow/interference/InterferenceKey.kt (renamed from opendc-simulator/opendc-simulator-failures/build.gradle.kts)16
-rw-r--r--opendc-simulator/opendc-simulator-flow/src/main/kotlin/org/opendc/simulator/flow/internal/Constants.kt (renamed from opendc-simulator/opendc-simulator-resources/src/main/kotlin/org/opendc/simulator/resources/SimResourceFlow.kt)7
-rw-r--r--opendc-simulator/opendc-simulator-flow/src/main/kotlin/org/opendc/simulator/flow/internal/Flags.kt44
-rw-r--r--opendc-simulator/opendc-simulator-flow/src/main/kotlin/org/opendc/simulator/flow/internal/FlowConsumerContextImpl.kt452
-rw-r--r--opendc-simulator/opendc-simulator-flow/src/main/kotlin/org/opendc/simulator/flow/internal/FlowDeque.kt116
-rw-r--r--opendc-simulator/opendc-simulator-flow/src/main/kotlin/org/opendc/simulator/flow/internal/FlowEngineImpl.kt215
-rw-r--r--opendc-simulator/opendc-simulator-flow/src/main/kotlin/org/opendc/simulator/flow/internal/FlowTimerQueue.kt195
-rw-r--r--opendc-simulator/opendc-simulator-flow/src/main/kotlin/org/opendc/simulator/flow/internal/MutableFlowCounters.kt56
-rw-r--r--opendc-simulator/opendc-simulator-flow/src/main/kotlin/org/opendc/simulator/flow/mux/FlowMultiplexer.kt100
-rw-r--r--opendc-simulator/opendc-simulator-flow/src/main/kotlin/org/opendc/simulator/flow/mux/ForwardingFlowMultiplexer.kt154
-rw-r--r--opendc-simulator/opendc-simulator-flow/src/main/kotlin/org/opendc/simulator/flow/mux/MaxMinFlowMultiplexer.kt789
-rw-r--r--opendc-simulator/opendc-simulator-flow/src/main/kotlin/org/opendc/simulator/flow/source/FixedFlowSource.kt (renamed from opendc-simulator/opendc-simulator-resources/src/main/kotlin/org/opendc/simulator/resources/consumer/SimWorkConsumer.kt)45
-rw-r--r--opendc-simulator/opendc-simulator-flow/src/main/kotlin/org/opendc/simulator/flow/source/FlowSourceBarrier.kt (renamed from opendc-simulator/opendc-simulator-resources/src/main/kotlin/org/opendc/simulator/resources/consumer/SimConsumerBarrier.kt)8
-rw-r--r--opendc-simulator/opendc-simulator-flow/src/main/kotlin/org/opendc/simulator/flow/source/FlowSourceRateAdapter.kt77
-rw-r--r--opendc-simulator/opendc-simulator-flow/src/main/kotlin/org/opendc/simulator/flow/source/TraceFlowSource.kt67
-rw-r--r--opendc-simulator/opendc-simulator-flow/src/test/kotlin/org/opendc/simulator/flow/FlowConsumerContextTest.kt104
-rw-r--r--opendc-simulator/opendc-simulator-flow/src/test/kotlin/org/opendc/simulator/flow/FlowForwarderTest.kt321
-rw-r--r--opendc-simulator/opendc-simulator-flow/src/test/kotlin/org/opendc/simulator/flow/FlowSinkTest.kt241
-rw-r--r--opendc-simulator/opendc-simulator-flow/src/test/kotlin/org/opendc/simulator/flow/mux/ForwardingFlowMultiplexerTest.kt154
-rw-r--r--opendc-simulator/opendc-simulator-flow/src/test/kotlin/org/opendc/simulator/flow/mux/MaxMinFlowMultiplexerTest.kt149
-rw-r--r--opendc-simulator/opendc-simulator-flow/src/test/kotlin/org/opendc/simulator/flow/source/FixedFlowSourceTest.kt (renamed from opendc-simulator/opendc-simulator-resources/src/test/kotlin/org/opendc/simulator/resources/SimWorkConsumerTest.kt)40
-rw-r--r--opendc-simulator/opendc-simulator-network/build.gradle.kts37
-rw-r--r--opendc-simulator/opendc-simulator-network/src/main/kotlin/org/opendc/simulator/network/SimNetworkLink.kt49
-rw-r--r--opendc-simulator/opendc-simulator-network/src/main/kotlin/org/opendc/simulator/network/SimNetworkPort.kt91
-rw-r--r--opendc-simulator/opendc-simulator-network/src/main/kotlin/org/opendc/simulator/network/SimNetworkSink.kt43
-rw-r--r--opendc-simulator/opendc-simulator-network/src/main/kotlin/org/opendc/simulator/network/SimNetworkSwitch.kt (renamed from opendc-simulator/opendc-simulator-failures/src/main/kotlin/org/opendc/simulator/failures/FaultInjector.kt)12
-rw-r--r--opendc-simulator/opendc-simulator-network/src/main/kotlin/org/opendc/simulator/network/SimNetworkSwitchVirtual.kt76
-rw-r--r--opendc-simulator/opendc-simulator-network/src/test/kotlin/org/opendc/simulator/network/SimNetworkLinkTest.kt89
-rw-r--r--opendc-simulator/opendc-simulator-network/src/test/kotlin/org/opendc/simulator/network/SimNetworkSinkTest.kt137
-rw-r--r--opendc-simulator/opendc-simulator-network/src/test/kotlin/org/opendc/simulator/network/SimNetworkSwitchVirtualTest.kt77
-rw-r--r--opendc-simulator/opendc-simulator-power/build.gradle.kts37
-rw-r--r--opendc-simulator/opendc-simulator-power/src/main/kotlin/org/opendc/simulator/power/SimPdu.kt92
-rw-r--r--opendc-simulator/opendc-simulator-power/src/main/kotlin/org/opendc/simulator/power/SimPowerInlet.kt48
-rw-r--r--opendc-simulator/opendc-simulator-power/src/main/kotlin/org/opendc/simulator/power/SimPowerOutlet.kt80
-rw-r--r--opendc-simulator/opendc-simulator-power/src/main/kotlin/org/opendc/simulator/power/SimPowerSource.kt (renamed from opendc-simulator/opendc-simulator-resources/src/main/kotlin/org/opendc/simulator/resources/SimResourceContext.kt)40
-rw-r--r--opendc-simulator/opendc-simulator-power/src/main/kotlin/org/opendc/simulator/power/SimUps.kt98
-rw-r--r--opendc-simulator/opendc-simulator-power/src/test/kotlin/org/opendc/simulator/power/SimPduTest.kt119
-rw-r--r--opendc-simulator/opendc-simulator-power/src/test/kotlin/org/opendc/simulator/power/SimPowerSourceTest.kt136
-rw-r--r--opendc-simulator/opendc-simulator-power/src/test/kotlin/org/opendc/simulator/power/SimUpsTest.kt101
-rw-r--r--opendc-simulator/opendc-simulator-resources/src/jmh/kotlin/org/opendc/simulator/resources/SimResourceBenchmarks.kt144
-rw-r--r--opendc-simulator/opendc-simulator-resources/src/main/kotlin/org/opendc/simulator/resources/SimAbstractResourceAggregator.kt178
-rw-r--r--opendc-simulator/opendc-simulator-resources/src/main/kotlin/org/opendc/simulator/resources/SimAbstractResourceContext.kt362
-rw-r--r--opendc-simulator/opendc-simulator-resources/src/main/kotlin/org/opendc/simulator/resources/SimResourceAggregatorMaxMin.kt71
-rw-r--r--opendc-simulator/opendc-simulator-resources/src/main/kotlin/org/opendc/simulator/resources/SimResourceCommand.kt52
-rw-r--r--opendc-simulator/opendc-simulator-resources/src/main/kotlin/org/opendc/simulator/resources/SimResourceConsumer.kt56
-rw-r--r--opendc-simulator/opendc-simulator-resources/src/main/kotlin/org/opendc/simulator/resources/SimResourceDistributorMaxMin.kt427
-rw-r--r--opendc-simulator/opendc-simulator-resources/src/main/kotlin/org/opendc/simulator/resources/SimResourceFlushable.kt37
-rw-r--r--opendc-simulator/opendc-simulator-resources/src/main/kotlin/org/opendc/simulator/resources/SimResourceProvider.kt93
-rw-r--r--opendc-simulator/opendc-simulator-resources/src/main/kotlin/org/opendc/simulator/resources/SimResourceScheduler.kt69
-rw-r--r--opendc-simulator/opendc-simulator-resources/src/main/kotlin/org/opendc/simulator/resources/SimResourceSchedulerTrampoline.kt95
-rw-r--r--opendc-simulator/opendc-simulator-resources/src/main/kotlin/org/opendc/simulator/resources/SimResourceSource.kt127
-rw-r--r--opendc-simulator/opendc-simulator-resources/src/main/kotlin/org/opendc/simulator/resources/SimResourceSwitchExclusive.kt98
-rw-r--r--opendc-simulator/opendc-simulator-resources/src/main/kotlin/org/opendc/simulator/resources/SimResourceSwitchMaxMin.kt118
-rw-r--r--opendc-simulator/opendc-simulator-resources/src/main/kotlin/org/opendc/simulator/resources/SimResourceTransformer.kt181
-rw-r--r--opendc-simulator/opendc-simulator-resources/src/main/kotlin/org/opendc/simulator/resources/consumer/SimSpeedConsumerAdapter.kt83
-rw-r--r--opendc-simulator/opendc-simulator-resources/src/main/kotlin/org/opendc/simulator/resources/consumer/SimTraceConsumer.kt73
-rw-r--r--opendc-simulator/opendc-simulator-resources/src/test/kotlin/org/opendc/simulator/resources/SimResourceAggregatorMaxMinTest.kt201
-rw-r--r--opendc-simulator/opendc-simulator-resources/src/test/kotlin/org/opendc/simulator/resources/SimResourceCommandTest.kt74
-rw-r--r--opendc-simulator/opendc-simulator-resources/src/test/kotlin/org/opendc/simulator/resources/SimResourceContextTest.kt147
-rw-r--r--opendc-simulator/opendc-simulator-resources/src/test/kotlin/org/opendc/simulator/resources/SimResourceSourceTest.kt341
-rw-r--r--opendc-simulator/opendc-simulator-resources/src/test/kotlin/org/opendc/simulator/resources/SimResourceSwitchExclusiveTest.kt175
-rw-r--r--opendc-simulator/opendc-simulator-resources/src/test/kotlin/org/opendc/simulator/resources/SimResourceSwitchMaxMinTest.kt189
-rw-r--r--opendc-simulator/opendc-simulator-resources/src/test/kotlin/org/opendc/simulator/resources/SimResourceTransformerTest.kt208
150 files changed, 9008 insertions, 5168 deletions
diff --git a/opendc-simulator/opendc-simulator-compute/build.gradle.kts b/opendc-simulator/opendc-simulator-compute/build.gradle.kts
index 4eb0be33..a2bb89c2 100644
--- a/opendc-simulator/opendc-simulator-compute/build.gradle.kts
+++ b/opendc-simulator/opendc-simulator-compute/build.gradle.kts
@@ -31,8 +31,11 @@ plugins {
dependencies {
api(platform(projects.opendcPlatform))
- api(projects.opendcSimulator.opendcSimulatorResources)
+ api(projects.opendcSimulator.opendcSimulatorFlow)
+ api(projects.opendcSimulator.opendcSimulatorPower)
+ api(projects.opendcSimulator.opendcSimulatorNetwork)
implementation(projects.opendcSimulator.opendcSimulatorCore)
implementation(projects.opendcUtils)
- implementation(libs.yaml)
+
+ testImplementation(libs.slf4j.simple)
}
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..cb52d24f 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,18 +25,20 @@ 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.kernel.SimFairShareHypervisor
+import org.opendc.simulator.compute.kernel.SimSpaceSharedHypervisor
+import org.opendc.simulator.compute.model.MachineModel
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.SimTrace
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.flow.FlowEngine
import org.openjdk.jmh.annotations.*
+import java.util.concurrent.ThreadLocalRandom
import java.util.concurrent.TimeUnit
@State(Scope.Thread)
@@ -45,68 +47,54 @@ import java.util.concurrent.TimeUnit
@Measurement(iterations = 5, time = 3, timeUnit = TimeUnit.SECONDS)
@OptIn(ExperimentalCoroutinesApi::class)
class SimMachineBenchmarks {
- private lateinit var scope: SimulationCoroutineScope
- private lateinit var scheduler: SimResourceScheduler
- private lateinit var machineModel: SimMachineModel
+ private lateinit var machineModel: MachineModel
+ private lateinit var trace: SimTrace
@Setup
fun setUp() {
- scope = SimulationCoroutineScope()
- scheduler = SimResourceSchedulerTrampoline(scope.coroutineContext, scope.clock)
-
val cpuNode = ProcessingNode("Intel", "Xeon", "amd64", 2)
- machineModel = SimMachineModel(
+ machineModel = MachineModel(
cpus = List(cpuNode.coreCount) { ProcessingUnit(cpuNode, it, 1000.0) },
memory = List(4) { MemoryUnit("Crucial", "MTA18ASF4G72AZ-3G2B1", 3200.0, 32_000) }
)
- }
- @State(Scope.Benchmark)
- class Workload {
- lateinit var trace: Sequence<SimTraceWorkload.Fragment>
-
- @Setup
- fun setUp() {
- trace = 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),
- )
+ val random = ThreadLocalRandom.current()
+ val builder = SimTrace.builder()
+ repeat(10000) {
+ val timestamp = it.toLong()
+ val deadline = timestamp + 1000
+ builder.add(timestamp, deadline, random.nextDouble(0.0, 4500.0), 1)
}
+ trace = builder.build()
}
@Benchmark
- fun benchmarkBareMetal(state: Workload) {
- return scope.runBlockingSimulation {
+ fun benchmarkBareMetal() {
+ return runBlockingSimulation {
+ val engine = FlowEngine(coroutineContext, clock)
val machine = SimBareMetalMachine(
- coroutineContext, clock, machineModel, PerformanceScalingGovernor(),
- SimpleScalingDriver(ConstantPowerModel(0.0))
+ engine, machineModel, SimplePowerDriver(ConstantPowerModel(0.0))
)
- return@runBlockingSimulation machine.run(SimTraceWorkload(state.trace))
+ return@runBlockingSimulation machine.run(SimTraceWorkload(trace))
}
}
@Benchmark
- fun benchmarkSpaceSharedHypervisor(state: Workload) {
- return scope.runBlockingSimulation {
+ fun benchmarkSpaceSharedHypervisor() {
+ return runBlockingSimulation {
+ val engine = FlowEngine(coroutineContext, clock)
val machine = SimBareMetalMachine(
- coroutineContext, clock, machineModel, PerformanceScalingGovernor(),
- SimpleScalingDriver(ConstantPowerModel(0.0))
+ engine, machineModel, SimplePowerDriver(ConstantPowerModel(0.0))
)
- val hypervisor = SimSpaceSharedHypervisor()
+ val hypervisor = SimSpaceSharedHypervisor(engine, null, null)
launch { machine.run(hypervisor) }
val vm = hypervisor.createMachine(machineModel)
try {
- return@runBlockingSimulation vm.run(SimTraceWorkload(state.trace))
+ return@runBlockingSimulation vm.run(SimTraceWorkload(trace))
} finally {
vm.close()
machine.close()
@@ -115,20 +103,20 @@ class SimMachineBenchmarks {
}
@Benchmark
- fun benchmarkFairShareHypervisorSingle(state: Workload) {
- return scope.runBlockingSimulation {
+ fun benchmarkFairShareHypervisorSingle() {
+ return runBlockingSimulation {
+ val engine = FlowEngine(coroutineContext, clock)
val machine = SimBareMetalMachine(
- coroutineContext, clock, machineModel, PerformanceScalingGovernor(),
- SimpleScalingDriver(ConstantPowerModel(0.0))
+ engine, machineModel, SimplePowerDriver(ConstantPowerModel(0.0))
)
- val hypervisor = SimFairShareHypervisor(scheduler)
+ val hypervisor = SimFairShareHypervisor(engine, null, null, null)
launch { machine.run(hypervisor) }
val vm = hypervisor.createMachine(machineModel)
try {
- return@runBlockingSimulation vm.run(SimTraceWorkload(state.trace))
+ return@runBlockingSimulation vm.run(SimTraceWorkload(trace))
} finally {
vm.close()
machine.close()
@@ -137,13 +125,13 @@ class SimMachineBenchmarks {
}
@Benchmark
- fun benchmarkFairShareHypervisorDouble(state: Workload) {
- return scope.runBlockingSimulation {
+ fun benchmarkFairShareHypervisorDouble() {
+ return runBlockingSimulation {
+ val engine = FlowEngine(coroutineContext, clock)
val machine = SimBareMetalMachine(
- coroutineContext, clock, machineModel, PerformanceScalingGovernor(),
- SimpleScalingDriver(ConstantPowerModel(0.0))
+ engine, machineModel, SimplePowerDriver(ConstantPowerModel(0.0))
)
- val hypervisor = SimFairShareHypervisor(scheduler)
+ val hypervisor = SimFairShareHypervisor(engine, null, null, null)
launch { machine.run(hypervisor) }
@@ -153,7 +141,7 @@ class SimMachineBenchmarks {
launch {
try {
- vm.run(SimTraceWorkload(state.trace))
+ vm.run(SimTraceWorkload(trace))
} finally {
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
deleted file mode 100644
index 713376e7..00000000
--- a/opendc-simulator/opendc-simulator-compute/src/main/kotlin/org/opendc/simulator/compute/SimAbstractHypervisor.kt
+++ /dev/null
@@ -1,189 +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
-
-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
index f6324e13..60a10f20 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
@@ -23,29 +23,53 @@
package org.opendc.simulator.compute
import kotlinx.coroutines.*
-import kotlinx.coroutines.flow.MutableStateFlow
-import kotlinx.coroutines.flow.StateFlow
+import org.opendc.simulator.compute.device.SimNetworkAdapter
+import org.opendc.simulator.compute.device.SimPeripheral
+import org.opendc.simulator.compute.model.MachineModel
import org.opendc.simulator.compute.model.MemoryUnit
+import org.opendc.simulator.compute.model.NetworkAdapter
+import org.opendc.simulator.compute.model.StorageDevice
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.flow.*
+import kotlin.coroutines.Continuation
+import kotlin.coroutines.resume
/**
* Abstract implementation of the [SimMachine] interface.
+ *
+ * @param engine The engine 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 {
- private val _usage = MutableStateFlow(0.0)
- override val usage: StateFlow<Double>
- get() = _usage
+public abstract class SimAbstractMachine(
+ protected val engine: FlowEngine,
+ private val parent: FlowConvergenceListener?,
+ final override val model: MachineModel
+) : SimMachine, FlowConvergenceListener {
+ /**
+ * The resources allocated for this machine.
+ */
+ public abstract val cpus: List<SimProcessingUnit>
+
+ /**
+ * The memory interface of the machine.
+ */
+ public val memory: SimMemory = Memory(FlowSink(engine, model.memory.sumOf { it.size }.toDouble()), model.memory)
/**
- * The speed of the CPU cores.
+ * The network interfaces available to the machine.
*/
- public val speed: DoubleArray
- get() = _speed
- private var _speed = doubleArrayOf()
+ public val net: List<SimNetworkInterface> = model.net.mapIndexed { i, adapter -> NetworkAdapterImpl(engine, adapter, i) }
+
+ /**
+ * The network interfaces available to the machine.
+ */
+ public val storage: List<SimStorageInterface> = model.storage.mapIndexed { i, device -> StorageDeviceImpl(engine, device, i) }
+
+ /**
+ * The peripherals of the machine.
+ */
+ public override val peripherals: List<SimPeripheral> = net.map { it as SimNetworkAdapter }
/**
* A flag to indicate that the machine is terminated.
@@ -53,75 +77,135 @@ public abstract class SimAbstractMachine(private val clock: Clock) : SimMachine
private var isTerminated = false
/**
- * The [CoroutineContext] to run in.
+ * The continuation to resume when the virtual machine workload has finished.
*/
- protected abstract val context: CoroutineContext
+ private var cont: Continuation<Unit>? = null
/**
- * The resources allocated for this machine.
+ * Converge the specified [SimWorkload] on this machine and suspend execution util the workload has finished.
+ */
+ override suspend fun run(workload: SimWorkload, meta: Map<String, Any>) {
+ check(!isTerminated) { "Machine is terminated" }
+ check(cont == null) { "A machine cannot run concurrently" }
+
+ val ctx = Context(meta)
+
+ return suspendCancellableCoroutine { cont ->
+ this.cont = cont
+
+ // Cancel all cpus on cancellation
+ cont.invokeOnCancellation {
+ this.cont = null
+ engine.batch {
+ for (cpu in cpus) {
+ cpu.cancel()
+ }
+ }
+ }
+
+ engine.batch { workload.onStart(ctx) }
+ }
+ }
+
+ override fun close() {
+ if (isTerminated) {
+ return
+ }
+
+ isTerminated = true
+ cancel()
+ }
+
+ override fun onConverge(now: Long, delta: Long) {
+ parent?.onConverge(now, delta)
+ }
+
+ /**
+ * Cancel the workload that is currently running on the machine.
*/
- protected abstract val cpus: List<SimProcessingUnit>
+ private fun cancel() {
+ engine.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 clock: Clock
- get() = this@SimAbstractMachine.clock
+ override val engine: FlowEngine
+ get() = this@SimAbstractMachine.engine
override val cpus: List<SimProcessingUnit> = this@SimAbstractMachine.cpus
- override val memory: List<MemoryUnit> = model.memory
+ override val memory: SimMemory = this@SimAbstractMachine.memory
+
+ override val net: List<SimNetworkInterface> = this@SimAbstractMachine.net
+
+ override val storage: List<SimStorageInterface> = this@SimAbstractMachine.storage
+
+ override fun close() = cancel()
}
/**
- * Run the specified [SimWorkload] on this machine and suspend execution util the workload has finished.
+ * The [SimMemory] implementation for a machine.
*/
- 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 }
+ private class Memory(source: FlowSink, override val models: List<MemoryUnit>) : SimMemory, FlowConsumer by source {
+ override fun toString(): String = "SimAbstractMachine.Memory"
+ }
- _speed = DoubleArray(model.cpus.size) { 0.0 }
- var totalSpeed = 0.0
+ /**
+ * The [SimNetworkAdapter] implementation for a machine.
+ */
+ private class NetworkAdapterImpl(
+ private val engine: FlowEngine,
+ model: NetworkAdapter,
+ index: Int
+ ) : SimNetworkAdapter(), SimNetworkInterface {
+ override val name: String = "eth$index"
- // Before the workload starts, initialize the initial power draw
- updateUsage(0.0)
+ override val bandwidth: Double = model.bandwidth
- workload.onStart(ctx)
+ override val provider: FlowConsumer
+ get() = _rx
- 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
+ override fun createConsumer(): FlowSource = _tx
- val oldSpeed = _speed[model.id]
- _speed[model.id] = newSpeed
- totalSpeed = totalSpeed - oldSpeed + newSpeed
+ override val tx: FlowConsumer
+ get() = _tx
+ private val _tx = FlowForwarder(engine)
- val newUsage = totalSpeed / totalCapacity
- if (_usage.value != newUsage) {
- updateUsage(totalSpeed / totalCapacity)
- }
- }
+ override val rx: FlowSource
+ get() = _rx
+ private val _rx = FlowForwarder(engine)
- launch { cpu.consume(adapter) }
- }
+ override fun toString(): String = "SimAbstractMachine.NetworkAdapterImpl[name=$name,bandwidth=$bandwidth]"
}
/**
- * This method is invoked when the usage of the machine is updated.
+ * The [SimStorageInterface] implementation for a machine.
*/
- protected open fun updateUsage(usage: Double) {
- _usage.value = usage
- }
+ private class StorageDeviceImpl(
+ engine: FlowEngine,
+ model: StorageDevice,
+ index: Int
+ ) : SimStorageInterface {
+ override val name: String = "disk$index"
- override fun close() {
- if (!isTerminated) {
- isTerminated = true
- cpus.forEach(SimProcessingUnit::close)
- }
+ override val capacity: Double = model.capacity
+
+ override val read: FlowConsumer = FlowSink(engine, model.readBandwidth)
+
+ override val write: FlowConsumer = FlowSink(engine, model.writeBandwidth)
+
+ override fun toString(): String = "SimAbstractMachine.StorageDeviceImpl[name=$name,capacity=$capacity]"
}
}
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..9140d31b 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,111 +22,93 @@
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.device.SimPsu
+import org.opendc.simulator.compute.model.MachineModel
import org.opendc.simulator.compute.model.ProcessingUnit
-import org.opendc.simulator.resources.*
-import org.opendc.utils.TimerScheduler
-import java.time.Clock
-import kotlin.coroutines.*
+import org.opendc.simulator.compute.power.PowerDriver
+import org.opendc.simulator.flow.*
+import org.opendc.simulator.flow.FlowEngine
+import kotlin.math.max
/**
* 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].
+ * 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 engine The [FlowEngine] to drive the simulation.
* @param model The machine model to simulate.
+ * @param powerDriver The power driver to use.
+ * @param psu The power supply of the machine.
+ * @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) {
+ engine: FlowEngine,
+ model: MachineModel,
+ powerDriver: PowerDriver,
+ public val psu: SimPsu = SimPsu(500.0, mapOf(1.0 to 1.0)),
+ parent: FlowConvergenceListener? = null,
+) : SimAbstractMachine(engine, parent, model) {
/**
- * The [Job] associated with this machine.
+ * The current power usage of the machine (without PSU loss) in W.
*/
- 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) }
+ public val powerUsage: Double
+ get() = _powerUsage
+ private var _powerUsage = 0.0
/**
- * Construct the [ScalingDriver.Logic] for this machine.
+ * The total energy usage of the machine (without PSU loss) in Joules.
*/
- private val scalingDriver = scalingDriver.createLogic(this)
+ public val energyUsage: Double
+ get() = _energyUsage
+ private var _energyUsage = 0.0
/**
- * The scaling contexts associated with each CPU.
+ * The processing units of the machine.
*/
- private val scalingGovernors = cpus.map { cpu ->
- scalingGovernor.createLogic(this.scalingDriver.createContext(cpu))
- }
-
- init {
- scalingGovernors.forEach { it.onStart() }
+ override val cpus: List<SimProcessingUnit> = model.cpus.map { cpu ->
+ Cpu(FlowSink(engine, cpu.frequency, this@SimBareMetalMachine), cpu)
}
/**
- * The power draw of the machine.
+ * The logic of the power driver.
*/
- public var powerDraw: Double = 0.0
- private set
+ private val powerDriverLogic = powerDriver.createLogic(this, cpus)
- override fun updateUsage(usage: Double) {
- super.updateUsage(usage)
+ private var _lastConverge = Long.MAX_VALUE
- scalingGovernors.forEach { it.onLimit() }
- powerDraw = scalingDriver.computePower()
- }
+ override fun onConverge(now: Long, delta: Long) {
+ // Update the PSU stage
+ psu.update()
- override fun close() {
- super.close()
+ val lastConverge = _lastConverge
+ _lastConverge = now
+ val duration = max(0, now - lastConverge)
+ if (duration > 0) {
+ // Compute the power and energy usage of the machine
+ _energyUsage += _powerUsage * (duration / 1000.0)
+ _powerUsage = powerDriverLogic.computePower()
+ }
+ }
- scope.cancel()
+ init {
+ psu.connect(powerDriverLogic)
}
/**
- * 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: FlowSink,
+ override val model: ProcessingUnit
+ ) : SimProcessingUnit, FlowConsumer by source {
+ override var capacity: Double
+ get() = source.capacity
+ set(value) {
+ // Clamp the capacity of the CPU between [0.0, maxFreq]
+ source.capacity = value.coerceIn(0.0, model.frequency)
+ }
+
+ 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
deleted file mode 100644
index 11aec2de..00000000
--- a/opendc-simulator/opendc-simulator-compute/src/main/kotlin/org/opendc/simulator/compute/SimFairShareHypervisor.kt
+++ /dev/null
@@ -1,56 +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
-
-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 scheduler: SimResourceScheduler, private val listener: SimHypervisor.Listener? = null) : SimAbstractHypervisor() {
-
- 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)
- }
- }
- )
- }
-}
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
index bfaa60bc..ab0b56ae 100644
--- 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
@@ -22,7 +22,8 @@
package org.opendc.simulator.compute
-import kotlinx.coroutines.flow.StateFlow
+import org.opendc.simulator.compute.device.SimPeripheral
+import org.opendc.simulator.compute.model.MachineModel
import org.opendc.simulator.compute.workload.SimWorkload
/**
@@ -32,15 +33,15 @@ public interface SimMachine : AutoCloseable {
/**
* The model of the machine containing its specifications.
*/
- public val model: SimMachineModel
+ public val model: MachineModel
/**
- * A [StateFlow] representing the CPU usage of the simulated machine.
+ * The peripherals attached to the machine.
*/
- public val usage: StateFlow<Double>
+ public val peripherals: List<SimPeripheral>
/**
- * Run the specified [SimWorkload] on this machine and suspend execution util the workload has finished.
+ * Converge 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())
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..1317f728 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
@@ -22,19 +22,18 @@
package org.opendc.simulator.compute
-import org.opendc.simulator.compute.model.MemoryUnit
-import java.time.Clock
+import org.opendc.simulator.flow.FlowEngine
/**
* 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 [FlowEngine] that simulates the machine.
*/
- public val clock: Clock
+ public val engine: FlowEngine
/**
* The metadata associated with the context.
@@ -47,7 +46,22 @@ public interface SimMachineContext {
public val cpus: List<SimProcessingUnit>
/**
- * The memory available on the machine
+ * The memory interface of the machine.
*/
- public val memory: List<MemoryUnit>
+ public val memory: SimMemory
+
+ /**
+ * The network interfaces available to the workload.
+ */
+ public val net: List<SimNetworkInterface>
+
+ /**
+ * The storage devices available to the workload.
+ */
+ public val storage: List<SimStorageInterface>
+
+ /**
+ * Stop the workload.
+ */
+ public override fun close()
}
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/SimMemory.kt
index 83b924d7..b1aef495 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/SimMemory.kt
@@ -22,16 +22,15 @@
package org.opendc.simulator.compute
-import java.time.Clock
-import kotlin.coroutines.CoroutineContext
+import org.opendc.simulator.compute.model.MemoryUnit
+import org.opendc.simulator.flow.FlowConsumer
/**
- * A [SimHypervisorProvider] for the [SimSpaceSharedHypervisor] implementation.
+ * An interface to control the memory usage of simulated workloads.
*/
-public class SimSpaceSharedHypervisorProvider : SimHypervisorProvider {
- override val id: String = "space-shared"
-
- override fun create(context: CoroutineContext, clock: Clock, listener: SimHypervisor.Listener?): SimHypervisor {
- return SimSpaceSharedHypervisor()
- }
+public interface SimMemory : FlowConsumer {
+ /**
+ * The models representing the static information of the memory units supporting this interface.
+ */
+ public val models: List<MemoryUnit>
}
diff --git a/opendc-simulator/opendc-simulator-compute/src/main/kotlin/org/opendc/simulator/compute/SimNetworkInterface.kt b/opendc-simulator/opendc-simulator-compute/src/main/kotlin/org/opendc/simulator/compute/SimNetworkInterface.kt
new file mode 100644
index 00000000..660b2871
--- /dev/null
+++ b/opendc-simulator/opendc-simulator-compute/src/main/kotlin/org/opendc/simulator/compute/SimNetworkInterface.kt
@@ -0,0 +1,51 @@
+/*
+ * 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.flow.FlowConsumer
+import org.opendc.simulator.flow.FlowSource
+
+/**
+ * A firmware interface to a network adapter.
+ */
+public interface SimNetworkInterface {
+ /**
+ * The name of the network interface.
+ */
+ public val name: String
+
+ /**
+ * The unidirectional bandwidth of the network interface in Mbps.
+ */
+ public val bandwidth: Double
+
+ /**
+ * The resource provider for the transmit channel of the network interface.
+ */
+ public val tx: FlowConsumer
+
+ /**
+ * The resource consumer for the receive channel of the network interface.
+ */
+ public val rx: FlowSource
+}
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..c9f36ece 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
@@ -23,19 +23,19 @@
package org.opendc.simulator.compute
import org.opendc.simulator.compute.model.ProcessingUnit
-import org.opendc.simulator.resources.SimResourceProvider
+import org.opendc.simulator.flow.FlowConsumer
/**
* A simulated processing unit.
*/
-public interface SimProcessingUnit : SimResourceProvider {
+public interface SimProcessingUnit : FlowConsumer {
/**
- * 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-resources/src/main/kotlin/org/opendc/simulator/resources/SimResourceEvent.kt b/opendc-simulator/opendc-simulator-compute/src/main/kotlin/org/opendc/simulator/compute/SimStorageInterface.kt
index 959427f1..3d648671 100644
--- a/opendc-simulator/opendc-simulator-resources/src/main/kotlin/org/opendc/simulator/resources/SimResourceEvent.kt
+++ b/opendc-simulator/opendc-simulator-compute/src/main/kotlin/org/opendc/simulator/compute/SimStorageInterface.kt
@@ -20,29 +20,31 @@
* SOFTWARE.
*/
-package org.opendc.simulator.resources
+package org.opendc.simulator.compute
+
+import org.opendc.simulator.flow.FlowConsumer
/**
- * A resource event that is communicated to the resource consumer.
+ * A firmware interface to a storage device.
*/
-public enum class SimResourceEvent {
+public interface SimStorageInterface {
/**
- * This event is emitted to the consumer when it has started.
+ * The name of the storage device.
*/
- Start,
+ public val name: String
/**
- * This event is emitted to the consumer when it has exited.
+ * The capacity of the storage device in MBs.
*/
- Exit,
+ public val capacity: Double
/**
- * This event is emitted to the consumer when it has started a new resource consumption or idle cycle.
+ * The resource provider for the read operations of the storage device.
*/
- Run,
+ public val read: FlowConsumer
/**
- * This event is emitted to the consumer when the capacity of the resource has changed.
+ * The resource consumer for the write operation of the storage device.
*/
- Capacity,
+ public val write: FlowConsumer
}
diff --git a/opendc-simulator/opendc-simulator-resources/src/main/kotlin/org/opendc/simulator/resources/SimResourceState.kt b/opendc-simulator/opendc-simulator-compute/src/main/kotlin/org/opendc/simulator/compute/device/SimNetworkAdapter.kt
index c72951d0..dfb4ecf3 100644
--- a/opendc-simulator/opendc-simulator-resources/src/main/kotlin/org/opendc/simulator/resources/SimResourceState.kt
+++ b/opendc-simulator/opendc-simulator-compute/src/main/kotlin/org/opendc/simulator/compute/device/SimNetworkAdapter.kt
@@ -20,24 +20,17 @@
* SOFTWARE.
*/
-package org.opendc.simulator.resources
+package org.opendc.simulator.compute.device
+
+import org.opendc.simulator.compute.SimMachine
+import org.opendc.simulator.network.SimNetworkPort
/**
- * The state of a resource provider.
+ * A simulated network interface card (NIC or network adapter) that can be attached to a [SimMachine].
*/
-public enum class SimResourceState {
- /**
- * The resource provider is pending and the resource is waiting to be consumed.
- */
- Pending,
-
- /**
- * The resource provider is active and the resource is currently being consumed.
- */
- Active,
-
+public abstract class SimNetworkAdapter : SimNetworkPort(), SimPeripheral {
/**
- * The resource provider is stopped and the resource cannot be consumed anymore.
+ * The unidirectional bandwidth of the network adapter in Mbps.
*/
- Stopped
+ public abstract val bandwidth: Double
}
diff --git a/opendc-simulator/opendc-simulator-compute/src/main/kotlin/org/opendc/simulator/compute/device/SimPeripheral.kt b/opendc-simulator/opendc-simulator-compute/src/main/kotlin/org/opendc/simulator/compute/device/SimPeripheral.kt
new file mode 100644
index 00000000..268271be
--- /dev/null
+++ b/opendc-simulator/opendc-simulator-compute/src/main/kotlin/org/opendc/simulator/compute/device/SimPeripheral.kt
@@ -0,0 +1,33 @@
+/*
+ * 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.device
+
+import org.opendc.simulator.compute.SimMachine
+
+/**
+ * A component that can be attached to a [SimMachine].
+ *
+ * This interface represents the physical view of the peripheral and should be used to configure the physical properties
+ * of the peripheral.
+ */
+public interface SimPeripheral
diff --git a/opendc-simulator/opendc-simulator-compute/src/main/kotlin/org/opendc/simulator/compute/device/SimPsu.kt b/opendc-simulator/opendc-simulator-compute/src/main/kotlin/org/opendc/simulator/compute/device/SimPsu.kt
new file mode 100644
index 00000000..09defbb5
--- /dev/null
+++ b/opendc-simulator/opendc-simulator-compute/src/main/kotlin/org/opendc/simulator/compute/device/SimPsu.kt
@@ -0,0 +1,114 @@
+/*
+ * 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.device
+
+import org.opendc.simulator.compute.power.PowerDriver
+import org.opendc.simulator.flow.FlowConnection
+import org.opendc.simulator.flow.FlowSource
+import org.opendc.simulator.power.SimPowerInlet
+import java.util.*
+
+/**
+ * A power supply of a [SimBareMetalMachine].
+ *
+ * @param ratedOutputPower The rated output power of the PSU.
+ * @param energyEfficiency The energy efficiency of the PSU for various power draws.
+ */
+public class SimPsu(
+ private val ratedOutputPower: Double,
+ energyEfficiency: Map<Double, Double>,
+) : SimPowerInlet() {
+ /**
+ * The power draw of the machine at this instant.
+ */
+ public val powerDraw: Double
+ get() = _powerDraw
+ private var _powerDraw = 0.0
+
+ /**
+ * The energy efficiency of the PSU at various power draws.
+ */
+ private val energyEfficiency = TreeMap(energyEfficiency)
+
+ /**
+ * The consumer context.
+ */
+ private var _ctx: FlowConnection? = null
+
+ /**
+ * The driver that is connected to the PSU.
+ */
+ private var _driver: PowerDriver.Logic? = null
+
+ init {
+ require(energyEfficiency.isNotEmpty()) { "Must specify at least one entry for energy efficiency of PSU" }
+ }
+
+ /**
+ * Update the power draw of the PSU.
+ */
+ public fun update() {
+ _ctx?.pull()
+ }
+
+ /**
+ * Connect the specified [PowerDriver.Logic] to this PSU.
+ */
+ public fun connect(driver: PowerDriver.Logic) {
+ check(_driver == null) { "PSU already connected" }
+ _driver = driver
+ update()
+ }
+
+ override fun createSource(): FlowSource = object : FlowSource {
+ override fun onStart(conn: FlowConnection, now: Long) {
+ _ctx = conn
+ conn.shouldSourceConverge = true
+ }
+
+ override fun onStop(conn: FlowConnection, now: Long, delta: Long) {
+ _ctx = null
+ }
+
+ override fun onPull(conn: FlowConnection, now: Long, delta: Long): Long {
+ val powerDraw = computePowerDraw(_driver?.computePower() ?: 0.0)
+ conn.push(powerDraw)
+ return Long.MAX_VALUE
+ }
+
+ override fun onConverge(conn: FlowConnection, now: Long, delta: Long) {
+ _powerDraw = conn.rate
+ }
+ }
+
+ /**
+ * Compute the power draw of the PSU including the power loss.
+ */
+ private fun computePowerDraw(load: Double): Double {
+ val loadPercentage = (load / ratedOutputPower).coerceIn(0.0, 1.0)
+ val efficiency = energyEfficiency.ceilingEntry(loadPercentage)?.value ?: 1.0
+ return load / efficiency
+ }
+
+ override fun toString(): String = "SimPsu[draw=$_powerDraw]"
+}
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
deleted file mode 100644
index 4c409887..00000000
--- a/opendc-simulator/opendc-simulator-compute/src/main/kotlin/org/opendc/simulator/compute/interference/PerformanceInterferenceModel.kt
+++ /dev/null
@@ -1,134 +0,0 @@
-/*
- * 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/kernel/SimAbstractHypervisor.kt b/opendc-simulator/opendc-simulator-compute/src/main/kotlin/org/opendc/simulator/compute/kernel/SimAbstractHypervisor.kt
new file mode 100644
index 00000000..f6d8f628
--- /dev/null
+++ b/opendc-simulator/opendc-simulator-compute/src/main/kotlin/org/opendc/simulator/compute/kernel/SimAbstractHypervisor.kt
@@ -0,0 +1,290 @@
+/*
+ * 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.kernel
+
+import org.opendc.simulator.compute.*
+import org.opendc.simulator.compute.kernel.cpufreq.ScalingGovernor
+import org.opendc.simulator.compute.kernel.cpufreq.ScalingPolicy
+import org.opendc.simulator.compute.kernel.interference.VmInterferenceDomain
+import org.opendc.simulator.compute.model.MachineModel
+import org.opendc.simulator.compute.model.ProcessingUnit
+import org.opendc.simulator.flow.*
+import org.opendc.simulator.flow.mux.FlowMultiplexer
+import kotlin.math.roundToLong
+
+/**
+ * Abstract implementation of the [SimHypervisor] interface.
+ *
+ * @param engine The [FlowEngine] to drive the simulation.
+ * @param scalingGovernor The scaling governor to use for scaling the CPU frequency of the underlying hardware.
+ */
+public abstract class SimAbstractHypervisor(
+ protected val engine: FlowEngine,
+ private val listener: FlowConvergenceListener?,
+ private val scalingGovernor: ScalingGovernor?,
+ protected val interferenceDomain: VmInterferenceDomain? = null
+) : SimHypervisor, FlowConvergenceListener {
+ /**
+ * The machine on which the hypervisor runs.
+ */
+ protected lateinit var context: SimMachineContext
+
+ /**
+ * The resource switch to use.
+ */
+ protected abstract val mux: FlowMultiplexer
+
+ /**
+ * The virtual machines running on this hypervisor.
+ */
+ private val _vms = mutableSetOf<VirtualMachine>()
+ override val vms: Set<SimMachine>
+ get() = _vms
+
+ /**
+ * The resource counters associated with the hypervisor.
+ */
+ public override val counters: SimHypervisorCounters
+ get() = _counters
+ private val _counters = CountersImpl(this)
+
+ /**
+ * The CPU capacity of the hypervisor in MHz.
+ */
+ override val cpuCapacity: Double
+ get() = mux.capacity
+
+ /**
+ * The CPU demand of the hypervisor in MHz.
+ */
+ override val cpuDemand: Double
+ get() = mux.demand
+
+ /**
+ * The CPU usage of the hypervisor in MHz.
+ */
+ override val cpuUsage: Double
+ get() = mux.rate
+
+ /**
+ * The scaling governors attached to the physical CPUs backing this hypervisor.
+ */
+ private val governors = mutableListOf<ScalingGovernor.Logic>()
+
+ /* SimHypervisor */
+ override fun createMachine(model: MachineModel, interferenceId: String?): SimVirtualMachine {
+ require(canFit(model)) { "Machine does not fit" }
+ val vm = VirtualMachine(model, interferenceId)
+ _vms.add(vm)
+ return vm
+ }
+
+ /* SimWorkload */
+ override fun onStart(ctx: SimMachineContext) {
+ context = ctx
+
+ _cpuCount = ctx.cpus.size
+ _cpuCapacity = ctx.cpus.sumOf { it.model.frequency }
+ _counters.d = _cpuCount / _cpuCapacity * 1000L
+
+ // Clear the existing outputs of the multiplexer
+ mux.clearOutputs()
+
+ for (cpu in ctx.cpus) {
+ val governor = scalingGovernor?.createLogic(ScalingPolicyImpl(cpu))
+ if (governor != null) {
+ governors.add(governor)
+ governor.onStart()
+ }
+
+ cpu.startConsumer(mux.newOutput())
+ }
+ }
+
+ private var _cpuCount = 0
+ private var _cpuCapacity = 0.0
+
+ /* FlowConvergenceListener */
+ override fun onConverge(now: Long, delta: Long) {
+ _counters.record()
+
+ val load = cpuDemand / cpuCapacity
+ for (governor in governors) {
+ governor.onLimit(load)
+ }
+
+ listener?.onConverge(now, delta)
+ }
+
+ /**
+ * A virtual machine running on the hypervisor.
+ *
+ * @param model The machine model of the virtual machine.
+ */
+ private inner class VirtualMachine(model: MachineModel, interferenceId: String? = null) : SimAbstractMachine(engine, parent = null, model), SimVirtualMachine {
+ /**
+ * The interference key of this virtual machine.
+ */
+ private val interferenceKey = interferenceId?.let { interferenceDomain?.join(interferenceId) }
+
+ /**
+ * The vCPUs of the machine.
+ */
+ override val cpus = model.cpus.map { VCpu(mux, mux.newInput(interferenceKey), it) }
+
+ /**
+ * The resource counters associated with the hypervisor.
+ */
+ override val counters: SimHypervisorCounters
+ get() = _counters
+ private val _counters = object : SimHypervisorCounters {
+ private val d = cpus.size / cpus.sumOf { it.model.frequency } * 1000
+
+ override val cpuActiveTime: Long
+ get() = (cpus.sumOf { it.counters.actual } * d).roundToLong()
+ override val cpuIdleTime: Long
+ get() = (cpus.sumOf { it.counters.actual + it.counters.remaining } * d).roundToLong()
+ override val cpuStealTime: Long
+ get() = (cpus.sumOf { it.counters.demand - it.counters.actual } * d).roundToLong()
+ override val cpuLostTime: Long = (cpus.sumOf { it.counters.interference } * d).roundToLong()
+ }
+
+ /**
+ * The CPU capacity of the hypervisor in MHz.
+ */
+ override val cpuCapacity: Double
+ get() = cpus.sumOf(FlowConsumer::capacity)
+
+ /**
+ * The CPU demand of the hypervisor in MHz.
+ */
+ override val cpuDemand: Double
+ get() = cpus.sumOf(FlowConsumer::demand)
+
+ /**
+ * The CPU usage of the hypervisor in MHz.
+ */
+ override val cpuUsage: Double
+ get() = cpus.sumOf(FlowConsumer::rate)
+
+ override fun close() {
+ super.close()
+
+ for (cpu in cpus) {
+ cpu.close()
+ }
+
+ _vms.remove(this)
+ if (interferenceKey != null) {
+ interferenceDomain?.leave(interferenceKey)
+ }
+ }
+ }
+
+ /**
+ * A [SimProcessingUnit] of a virtual machine.
+ */
+ private class VCpu(
+ private val switch: FlowMultiplexer,
+ private val source: FlowConsumer,
+ override val model: ProcessingUnit
+ ) : SimProcessingUnit, FlowConsumer by source {
+ override var capacity: Double
+ get() = source.capacity
+ set(_) {
+ // Ignore capacity changes
+ }
+
+ override fun toString(): String = "SimAbstractHypervisor.VCpu[model=$model]"
+
+ /**
+ * Close the CPU
+ */
+ fun close() {
+ switch.removeInput(source)
+ }
+ }
+
+ /**
+ * A [ScalingPolicy] for a physical CPU of the hypervisor.
+ */
+ private class ScalingPolicyImpl(override val cpu: SimProcessingUnit) : ScalingPolicy {
+ override var target: Double
+ get() = cpu.capacity
+ set(value) {
+ cpu.capacity = value
+ }
+
+ override val max: Double = cpu.model.frequency
+
+ override val min: Double = 0.0
+ }
+
+ /**
+ * Implementation of [SimHypervisorCounters].
+ */
+ private class CountersImpl(private val hv: SimAbstractHypervisor) : SimHypervisorCounters {
+ @JvmField var d = 1.0 // Number of CPUs divided by total CPU capacity
+
+ override val cpuActiveTime: Long
+ get() = _cpuTime[0]
+ override val cpuIdleTime: Long
+ get() = _cpuTime[1]
+ override val cpuStealTime: Long
+ get() = _cpuTime[2]
+ override val cpuLostTime: Long
+ get() = _cpuTime[3]
+
+ private val _cpuTime = LongArray(4)
+ private val _previous = DoubleArray(4)
+
+ /**
+ * Record the CPU time of the hypervisor.
+ */
+ fun record() {
+ val cpuTime = _cpuTime
+ val previous = _previous
+ val counters = hv.mux.counters
+
+ val demand = counters.demand
+ val actual = counters.actual
+ val remaining = counters.remaining
+ val interference = counters.interference
+
+ val demandDelta = demand - previous[0]
+ val actualDelta = actual - previous[1]
+ val remainingDelta = remaining - previous[2]
+ val interferenceDelta = interference - previous[3]
+
+ previous[0] = demand
+ previous[1] = actual
+ previous[2] = remaining
+ previous[3] = interference
+
+ cpuTime[0] += (actualDelta * d).roundToLong()
+ cpuTime[1] += (remainingDelta * d).roundToLong()
+ cpuTime[2] += ((demandDelta - actualDelta) * d).roundToLong()
+ cpuTime[3] += (interferenceDelta * d).roundToLong()
+ }
+ }
+}
diff --git a/opendc-simulator/opendc-simulator-compute/src/main/kotlin/org/opendc/simulator/compute/kernel/SimFairShareHypervisor.kt b/opendc-simulator/opendc-simulator-compute/src/main/kotlin/org/opendc/simulator/compute/kernel/SimFairShareHypervisor.kt
new file mode 100644
index 00000000..36f76650
--- /dev/null
+++ b/opendc-simulator/opendc-simulator-compute/src/main/kotlin/org/opendc/simulator/compute/kernel/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.kernel
+
+import org.opendc.simulator.compute.SimMachine
+import org.opendc.simulator.compute.kernel.cpufreq.ScalingGovernor
+import org.opendc.simulator.compute.kernel.interference.VmInterferenceDomain
+import org.opendc.simulator.compute.model.MachineModel
+import org.opendc.simulator.compute.workload.SimWorkload
+import org.opendc.simulator.flow.FlowConvergenceListener
+import org.opendc.simulator.flow.FlowEngine
+import org.opendc.simulator.flow.mux.FlowMultiplexer
+import org.opendc.simulator.flow.mux.MaxMinFlowMultiplexer
+
+/**
+ * A [SimHypervisor] that distributes the computing requirements of multiple [SimWorkload]s on a single [SimMachine]
+ * concurrently using weighted fair sharing.
+ *
+ * @param engine The [FlowEngine] to manage the machine's resources.
+ * @param listener The listener for the convergence of the system.
+ * @param scalingGovernor The CPU frequency scaling governor to use for the hypervisor.
+ * @param interferenceDomain The resource interference domain to which the hypervisor belongs.
+ */
+public class SimFairShareHypervisor(
+ engine: FlowEngine,
+ listener: FlowConvergenceListener?,
+ scalingGovernor: ScalingGovernor?,
+ interferenceDomain: VmInterferenceDomain?,
+) : SimAbstractHypervisor(engine, listener, scalingGovernor, interferenceDomain) {
+ /**
+ * The multiplexer that distributes the computing capacity.
+ */
+ override val mux: FlowMultiplexer = MaxMinFlowMultiplexer(engine, this, interferenceDomain)
+
+ override fun canFit(model: MachineModel): Boolean = true
+}
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/kernel/SimFairShareHypervisorProvider.kt
index 2ab3ea09..3136f4c8 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/kernel/SimFairShareHypervisorProvider.kt
@@ -20,11 +20,12 @@
* SOFTWARE.
*/
-package org.opendc.simulator.compute
+package org.opendc.simulator.compute.kernel
-import org.opendc.simulator.resources.SimResourceSchedulerTrampoline
-import java.time.Clock
-import kotlin.coroutines.CoroutineContext
+import org.opendc.simulator.compute.kernel.cpufreq.ScalingGovernor
+import org.opendc.simulator.compute.kernel.interference.VmInterferenceDomain
+import org.opendc.simulator.flow.FlowConvergenceListener
+import org.opendc.simulator.flow.FlowEngine
/**
* A [SimHypervisorProvider] for the [SimFairShareHypervisor] implementation.
@@ -32,7 +33,10 @@ 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(
+ engine: FlowEngine,
+ listener: FlowConvergenceListener?,
+ scalingGovernor: ScalingGovernor?,
+ interferenceDomain: VmInterferenceDomain?,
+ ): SimHypervisor = SimFairShareHypervisor(engine, listener, scalingGovernor, interferenceDomain)
}
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/kernel/SimHypervisor.kt
index 4a233fec..57d4cf20 100644
--- 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/kernel/SimHypervisor.kt
@@ -1,5 +1,5 @@
/*
- * Copyright (c) 2020 AtLarge Research
+ * 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
@@ -20,14 +20,15 @@
* SOFTWARE.
*/
-package org.opendc.simulator.compute
+package org.opendc.simulator.compute.kernel
-import org.opendc.simulator.compute.interference.PerformanceInterferenceModel
+import org.opendc.simulator.compute.SimMachine
+import org.opendc.simulator.compute.model.MachineModel
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].
+ * to another [SimMachine].
*/
public interface SimHypervisor : SimWorkload {
/**
@@ -36,36 +37,35 @@ public interface SimHypervisor : SimWorkload {
public val vms: Set<SimMachine>
/**
+ * The resource counters associated with the hypervisor.
+ */
+ public val counters: SimHypervisorCounters
+
+ /**
+ * The CPU usage of the hypervisor in MHz.
+ */
+ public val cpuUsage: Double
+
+ /**
+ * The CPU usage of the hypervisor in MHz.
+ */
+ public val cpuDemand: Double
+
+ /**
+ * The CPU capacity of the hypervisor in MHz.
+ */
+ public val cpuCapacity: Double
+
+ /**
* Determine whether the specified machine characterized by [model] can fit on this hypervisor at this moment.
*/
- public fun canFit(model: SimMachineModel): Boolean
+ public fun canFit(model: MachineModel): 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.
+ * @param interferenceId An identifier for the interference model.
*/
- 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
- )
- }
+ public fun createMachine(model: MachineModel, interferenceId: String? = null): SimVirtualMachine
}
diff --git a/opendc-simulator/opendc-simulator-resources/src/main/kotlin/org/opendc/simulator/resources/SimResourceSwitch.kt b/opendc-simulator/opendc-simulator-compute/src/main/kotlin/org/opendc/simulator/compute/kernel/SimHypervisorCounters.kt
index 53fec16a..030d9c5f 100644
--- a/opendc-simulator/opendc-simulator-resources/src/main/kotlin/org/opendc/simulator/resources/SimResourceSwitch.kt
+++ b/opendc-simulator/opendc-simulator-compute/src/main/kotlin/org/opendc/simulator/compute/kernel/SimHypervisorCounters.kt
@@ -20,29 +20,29 @@
* SOFTWARE.
*/
-package org.opendc.simulator.resources
+package org.opendc.simulator.compute.kernel
/**
- * A [SimResourceSwitch] enables switching of capacity of multiple resources between multiple consumers.
+ * Performance counters of a [SimHypervisor].
*/
-public interface SimResourceSwitch : AutoCloseable {
+public interface SimHypervisorCounters {
/**
- * The output resource providers to which resource consumers can be attached.
+ * The amount of time (in milliseconds) the CPUs of the hypervisor were actively running.
*/
- public val outputs: Set<SimResourceProvider>
+ public val cpuActiveTime: Long
/**
- * The input resources that will be switched between the output providers.
+ * The amount of time (in milliseconds) the CPUs of the hypervisor were idle.
*/
- public val inputs: Set<SimResourceProvider>
+ public val cpuIdleTime: Long
/**
- * Add an output to the switch with the specified [capacity].
+ * The amount of CPU time (in milliseconds) that virtual machines were ready to run, but were not able to.
*/
- public fun addOutput(capacity: Double): SimResourceProvider
+ public val cpuStealTime: Long
/**
- * Add the specified [input] to the switch.
+ * The amount of CPU time (in milliseconds) that was lost due to interference between virtual machines.
*/
- public fun addInput(input: SimResourceProvider)
+ public val cpuLostTime: Long
}
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/kernel/SimHypervisorProvider.kt
index b66020f4..483217af 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/kernel/SimHypervisorProvider.kt
@@ -20,10 +20,12 @@
* SOFTWARE.
*/
-package org.opendc.simulator.compute
+package org.opendc.simulator.compute.kernel
-import java.time.Clock
-import kotlin.coroutines.CoroutineContext
+import org.opendc.simulator.compute.kernel.cpufreq.ScalingGovernor
+import org.opendc.simulator.compute.kernel.interference.VmInterferenceDomain
+import org.opendc.simulator.flow.FlowConvergenceListener
+import org.opendc.simulator.flow.FlowEngine
/**
* A service provider interface for constructing a [SimHypervisor].
@@ -38,7 +40,12 @@ public interface SimHypervisorProvider {
public val id: String
/**
- * Create a [SimHypervisor] instance with the specified [listener].
+ * Create a new [SimHypervisor] instance.
*/
- public fun create(context: CoroutineContext, clock: Clock, listener: SimHypervisor.Listener? = null): SimHypervisor
+ public fun create(
+ engine: FlowEngine,
+ listener: FlowConvergenceListener? = null,
+ scalingGovernor: ScalingGovernor? = null,
+ interferenceDomain: VmInterferenceDomain? = null,
+ ): SimHypervisor
}
diff --git a/opendc-simulator/opendc-simulator-compute/src/main/kotlin/org/opendc/simulator/compute/kernel/SimSpaceSharedHypervisor.kt b/opendc-simulator/opendc-simulator-compute/src/main/kotlin/org/opendc/simulator/compute/kernel/SimSpaceSharedHypervisor.kt
new file mode 100644
index 00000000..82f8df38
--- /dev/null
+++ b/opendc-simulator/opendc-simulator-compute/src/main/kotlin/org/opendc/simulator/compute/kernel/SimSpaceSharedHypervisor.kt
@@ -0,0 +1,45 @@
+/*
+ * 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.kernel
+
+import org.opendc.simulator.compute.kernel.cpufreq.ScalingGovernor
+import org.opendc.simulator.compute.model.MachineModel
+import org.opendc.simulator.flow.FlowConvergenceListener
+import org.opendc.simulator.flow.FlowEngine
+import org.opendc.simulator.flow.mux.FlowMultiplexer
+import org.opendc.simulator.flow.mux.ForwardingFlowMultiplexer
+
+/**
+ * A [SimHypervisor] that allocates its sub-resources exclusively for the virtual machine that it hosts.
+ */
+public class SimSpaceSharedHypervisor(
+ engine: FlowEngine,
+ listener: FlowConvergenceListener?,
+ scalingGovernor: ScalingGovernor?,
+) : SimAbstractHypervisor(engine, listener, scalingGovernor) {
+ override val mux: FlowMultiplexer = ForwardingFlowMultiplexer(engine)
+
+ override fun canFit(model: MachineModel): Boolean {
+ return mux.outputs.size - mux.inputs.size >= model.cpus.size
+ }
+}
diff --git a/opendc-simulator/opendc-simulator-compute/src/main/kotlin/org/opendc/simulator/compute/kernel/SimSpaceSharedHypervisorProvider.kt b/opendc-simulator/opendc-simulator-compute/src/main/kotlin/org/opendc/simulator/compute/kernel/SimSpaceSharedHypervisorProvider.kt
new file mode 100644
index 00000000..dd6fb0b1
--- /dev/null
+++ b/opendc-simulator/opendc-simulator-compute/src/main/kotlin/org/opendc/simulator/compute/kernel/SimSpaceSharedHypervisorProvider.kt
@@ -0,0 +1,42 @@
+/*
+ * 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.kernel
+
+import org.opendc.simulator.compute.kernel.cpufreq.ScalingGovernor
+import org.opendc.simulator.compute.kernel.interference.VmInterferenceDomain
+import org.opendc.simulator.flow.FlowConvergenceListener
+import org.opendc.simulator.flow.FlowEngine
+
+/**
+ * A [SimHypervisorProvider] for the [SimSpaceSharedHypervisor] implementation.
+ */
+public class SimSpaceSharedHypervisorProvider : SimHypervisorProvider {
+ override val id: String = "space-shared"
+
+ override fun create(
+ engine: FlowEngine,
+ listener: FlowConvergenceListener?,
+ scalingGovernor: ScalingGovernor?,
+ interferenceDomain: VmInterferenceDomain?,
+ ): SimHypervisor = SimSpaceSharedHypervisor(engine, listener, scalingGovernor)
+}
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/kernel/SimVirtualMachine.kt
index 18338079..36219ef2 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/kernel/SimVirtualMachine.kt
@@ -20,27 +20,31 @@
* SOFTWARE.
*/
-package org.opendc.simulator.compute.cpufreq
+package org.opendc.simulator.compute.kernel
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 virtual [SimMachine] running on top of another [SimMachine].
*/
-public interface ScalingContext {
+public interface SimVirtualMachine : SimMachine {
/**
- * The machine the processing unit belongs to.
+ * The resource counters associated with the virtual machine.
*/
- public val machine: SimMachine
+ public val counters: SimHypervisorCounters
/**
- * The processing unit associated with this context.
+ * The CPU usage of the VM in MHz.
*/
- public val cpu: SimProcessingUnit
+ public val cpuUsage: Double
/**
- * Target the processor to run at the specified target [frequency][freq].
+ * The CPU usage of the VM in MHz.
*/
- public fun setTarget(freq: Double)
+ public val cpuDemand: Double
+
+ /**
+ * The CPU capacity of the VM in MHz.
+ */
+ public val cpuCapacity: Double
}
diff --git a/opendc-simulator/opendc-simulator-compute/src/main/kotlin/org/opendc/simulator/compute/kernel/cpufreq/ConservativeScalingGovernor.kt b/opendc-simulator/opendc-simulator-compute/src/main/kotlin/org/opendc/simulator/compute/kernel/cpufreq/ConservativeScalingGovernor.kt
new file mode 100644
index 00000000..1a03221d
--- /dev/null
+++ b/opendc-simulator/opendc-simulator-compute/src/main/kotlin/org/opendc/simulator/compute/kernel/cpufreq/ConservativeScalingGovernor.kt
@@ -0,0 +1,66 @@
+/*
+ * 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.kernel.cpufreq
+
+/**
+ * A CPUFreq [ScalingGovernor] that models the conservative scaling governor in the Linux kernel.
+ */
+public class ConservativeScalingGovernor(public val threshold: Double = 0.8, private val stepSize: Double = -1.0) :
+ ScalingGovernor {
+ override fun createLogic(policy: ScalingPolicy): ScalingGovernor.Logic = object : ScalingGovernor.Logic {
+ /**
+ * The step size to use.
+ */
+ private val stepSize = if (this@ConservativeScalingGovernor.stepSize < 0) {
+ // https://github.com/torvalds/linux/blob/master/drivers/cpufreq/cpufreq_conservative.c#L33
+ policy.max * 0.05
+ } else {
+ this@ConservativeScalingGovernor.stepSize.coerceAtMost(policy.max)
+ }
+
+ /**
+ * The previous load of the CPU.
+ */
+ private var previousLoad = threshold
+
+ override fun onStart() {
+ policy.target = policy.min
+ }
+
+ override fun onLimit(load: Double) {
+ val currentTarget = policy.target
+ if (load > threshold) {
+ // Check for load increase (see: https://github.com/torvalds/linux/blob/master/drivers/cpufreq/cpufreq_conservative.c#L102)
+ val step = when {
+ load > previousLoad -> stepSize
+ load < previousLoad -> -stepSize
+ else -> 0.0
+ }
+ policy.target = (currentTarget + step).coerceIn(policy.min, policy.max)
+ }
+ previousLoad = load
+ }
+ }
+
+ override fun toString(): String = "ConservativeScalingGovernor[threshold=$threshold,stepSize=$stepSize]"
+}
diff --git a/opendc-simulator/opendc-simulator-compute/src/main/kotlin/org/opendc/simulator/compute/kernel/cpufreq/OnDemandScalingGovernor.kt b/opendc-simulator/opendc-simulator-compute/src/main/kotlin/org/opendc/simulator/compute/kernel/cpufreq/OnDemandScalingGovernor.kt
new file mode 100644
index 00000000..aef15ce9
--- /dev/null
+++ b/opendc-simulator/opendc-simulator-compute/src/main/kotlin/org/opendc/simulator/compute/kernel/cpufreq/OnDemandScalingGovernor.kt
@@ -0,0 +1,50 @@
+/*
+ * 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.kernel.cpufreq
+
+/**
+ * A CPUFreq [ScalingGovernor] that models the on-demand scaling governor in the Linux kernel.
+ */
+public class OnDemandScalingGovernor(public val threshold: Double = 0.8) : ScalingGovernor {
+ override fun createLogic(policy: ScalingPolicy): ScalingGovernor.Logic = object : ScalingGovernor.Logic {
+ /**
+ * The multiplier used for the linear frequency scaling.
+ */
+ private val multiplier = (policy.max - policy.min) / 100
+
+ override fun onStart() {
+ policy.target = policy.min
+ }
+
+ override fun onLimit(load: Double) {
+ policy.target = if (load < threshold) {
+ /* Proportional scaling (see: https://github.com/torvalds/linux/blob/master/drivers/cpufreq/cpufreq_ondemand.c#L151). */
+ policy.min + load * multiplier
+ } else {
+ policy.max
+ }
+ }
+ }
+
+ override fun toString(): String = "OnDemandScalingGovernor[threshold=$threshold]"
+}
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/kernel/cpufreq/PerformanceScalingGovernor.kt
index 96f8775a..13109a9a 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/kernel/cpufreq/PerformanceScalingGovernor.kt
@@ -20,17 +20,17 @@
* SOFTWARE.
*/
-package org.opendc.simulator.compute.cpufreq
+package org.opendc.simulator.compute.kernel.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 createLogic(policy: ScalingPolicy): ScalingGovernor.Logic = object : ScalingGovernor.Logic {
+ override fun onStart() {
+ policy.target = policy.max
}
-
- override fun toString(): String = "PerformanceScalingGovernor.Logic"
}
+
+ override fun toString(): String = "PerformanceScalingGovernor"
}
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/kernel/cpufreq/PowerSaveScalingGovernor.kt
index ddbe1ca0..32c0703a 100644
--- 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/kernel/cpufreq/PowerSaveScalingGovernor.kt
@@ -20,17 +20,17 @@
* SOFTWARE.
*/
-package org.opendc.simulator.compute.cpufreq
+package org.opendc.simulator.compute.kernel.cpufreq
/**
- * A CPUFreq [ScalingGovernor] that requests the frequency based on the utilization of the machine.
+ * A CPUFreq [ScalingGovernor] that causes the lowest possible frequency to be requested from the resource.
*/
-public class DemandScalingGovernor : ScalingGovernor {
- override fun createLogic(ctx: ScalingContext): ScalingGovernor.Logic = object : ScalingGovernor.Logic {
- override fun onLimit() {
- ctx.setTarget(ctx.cpu.speed)
+public class PowerSaveScalingGovernor : ScalingGovernor {
+ override fun createLogic(policy: ScalingPolicy): ScalingGovernor.Logic = object : ScalingGovernor.Logic {
+ override fun onStart() {
+ policy.target = policy.min
}
-
- override fun toString(): String = "DemandScalingGovernor.Logic"
}
+
+ override fun toString(): String = "PowerSaveScalingGovernor"
}
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/kernel/cpufreq/ScalingGovernor.kt
index c9aea580..d33827db 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/kernel/cpufreq/ScalingGovernor.kt
@@ -20,7 +20,7 @@
* SOFTWARE.
*/
-package org.opendc.simulator.compute.cpufreq
+package org.opendc.simulator.compute.kernel.cpufreq
/**
* A [ScalingGovernor] in the CPUFreq subsystem of OpenDC is responsible for scaling the frequency of simulated CPUs
@@ -33,9 +33,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 [policy]
*/
- public fun createLogic(ctx: ScalingContext): Logic
+ public fun createLogic(policy: ScalingPolicy): Logic
/**
* The logic of the scaling governor.
@@ -48,7 +48,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/kernel/cpufreq/ScalingPolicy.kt b/opendc-simulator/opendc-simulator-compute/src/main/kotlin/org/opendc/simulator/compute/kernel/cpufreq/ScalingPolicy.kt
new file mode 100644
index 00000000..f9351896
--- /dev/null
+++ b/opendc-simulator/opendc-simulator-compute/src/main/kotlin/org/opendc/simulator/compute/kernel/cpufreq/ScalingPolicy.kt
@@ -0,0 +1,51 @@
+/*
+ * 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.kernel.cpufreq
+
+import org.opendc.simulator.compute.SimProcessingUnit
+
+/**
+ * An interface that holds the state managed by a [ScalingGovernor] and used by the underlying machine to control the
+ * CPU frequencies.
+ */
+public interface ScalingPolicy {
+ /**
+ * The processing unit that is associated with this policy.
+ */
+ public val cpu: SimProcessingUnit
+
+ /**
+ * The target frequency which the CPU should attempt to attain.
+ */
+ public var target: Double
+
+ /**
+ * The minimum frequency to which the CPU may scale.
+ */
+ public val min: Double
+
+ /**
+ * The maximum frequency to which the CPU may scale.
+ */
+ public val max: 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/kernel/interference/VmInterferenceDomain.kt
index fd8e546f..b737d61a 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/kernel/interference/VmInterferenceDomain.kt
@@ -20,19 +20,24 @@
* SOFTWARE.
*/
-package org.opendc.simulator.compute
+package org.opendc.simulator.compute.kernel.interference
-import org.opendc.simulator.resources.*
+import org.opendc.simulator.flow.interference.InterferenceDomain
+import org.opendc.simulator.flow.interference.InterferenceKey
/**
- * A [SimHypervisor] that allocates its sub-resources exclusively for the virtual machine that it hosts.
+ * The interference domain of a hypervisor.
*/
-public class SimSpaceSharedHypervisor : SimAbstractHypervisor() {
- override fun canFit(model: SimMachineModel, switch: SimResourceSwitch): Boolean {
- return switch.inputs.size - switch.outputs.size >= model.cpus.size
- }
+public interface VmInterferenceDomain : InterferenceDomain {
+ /**
+ * Join this interference domain.
+ *
+ * @param id The identifier of the virtual machine.
+ */
+ public fun join(id: String): InterferenceKey
- override fun createSwitch(ctx: SimMachineContext): SimResourceSwitch {
- return SimResourceSwitchExclusive()
- }
+ /**
+ * Leave this interference domain.
+ */
+ public fun leave(key: InterferenceKey)
}
diff --git a/opendc-simulator/opendc-simulator-resources/src/main/kotlin/org/opendc/simulator/resources/SimResourceDistributor.kt b/opendc-simulator/opendc-simulator-compute/src/main/kotlin/org/opendc/simulator/compute/kernel/interference/VmInterferenceGroup.kt
index b2759b7f..708ddede 100644
--- a/opendc-simulator/opendc-simulator-resources/src/main/kotlin/org/opendc/simulator/resources/SimResourceDistributor.kt
+++ b/opendc-simulator/opendc-simulator-compute/src/main/kotlin/org/opendc/simulator/compute/kernel/interference/VmInterferenceGroup.kt
@@ -20,24 +20,25 @@
* SOFTWARE.
*/
-package org.opendc.simulator.resources
+package org.opendc.simulator.compute.kernel.interference
/**
- * A [SimResourceDistributor] distributes the capacity of some resource over multiple resource consumers.
+ * A group of virtual machines that together can interfere when operating on the same resources, causing performance
+ * variability.
*/
-public interface SimResourceDistributor : AutoCloseable {
+public data class VmInterferenceGroup(
/**
- * The output resource providers to which resource consumers can be attached.
+ * The minimum load of the host before the interference occurs.
*/
- public val outputs: Set<SimResourceProvider>
+ public val targetLoad: Double,
/**
- * The input resource that will be distributed over the consumers.
+ * A score in [0, 1] representing the performance variability as a result of resource interference.
*/
- public val input: SimResourceProvider
+ public val score: Double,
/**
- * Add an output to the switch with the specified [capacity].
+ * The members of this interference group.
*/
- public fun addOutput(capacity: Double): SimResourceProvider
-}
+ public val members: Set<String>
+)
diff --git a/opendc-simulator/opendc-simulator-compute/src/main/kotlin/org/opendc/simulator/compute/kernel/interference/VmInterferenceModel.kt b/opendc-simulator/opendc-simulator-compute/src/main/kotlin/org/opendc/simulator/compute/kernel/interference/VmInterferenceModel.kt
new file mode 100644
index 00000000..b3d72507
--- /dev/null
+++ b/opendc-simulator/opendc-simulator-compute/src/main/kotlin/org/opendc/simulator/compute/kernel/interference/VmInterferenceModel.kt
@@ -0,0 +1,170 @@
+/*
+ * 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.kernel.interference
+
+import org.opendc.simulator.flow.interference.InterferenceKey
+import java.util.*
+
+/**
+ * An interference model that models the resource interference between virtual machines on a host.
+ *
+ * @param groups The groups of virtual machines that interfere with each other.
+ * @param random The [Random] instance to select the affected virtual machines.
+ */
+public class VmInterferenceModel(
+ private val groups: List<VmInterferenceGroup>,
+ private val random: Random = Random(0)
+) {
+ /**
+ * Construct a new [VmInterferenceDomain].
+ */
+ public fun newDomain(): VmInterferenceDomain = object : VmInterferenceDomain {
+ /**
+ * The stateful groups of this domain.
+ */
+ private val groups = this@VmInterferenceModel.groups.map { GroupContext(it) }
+
+ /**
+ * The set of keys active in this domain.
+ */
+ private val keys = mutableSetOf<InterferenceKeyImpl>()
+
+ override fun join(id: String): InterferenceKey {
+ val key = InterferenceKeyImpl(id, groups.filter { id in it }.sortedBy { it.group.targetLoad })
+ keys += key
+ return key
+ }
+
+ override fun leave(key: InterferenceKey) {
+ if (key is InterferenceKeyImpl) {
+ keys -= key
+ key.leave()
+ }
+ }
+
+ override fun apply(key: InterferenceKey?, load: Double): Double {
+ if (key == null || key !is InterferenceKeyImpl) {
+ return 1.0
+ }
+
+ val ctx = key.findGroup(load)
+ val group = ctx?.group
+
+ // Apply performance penalty to (on average) only one of the VMs
+ return if (group != null && random.nextInt(group.members.size) == 0) {
+ group.score
+ } else {
+ 1.0
+ }
+ }
+
+ override fun toString(): String = "VmInterferenceDomain"
+ }
+
+ /**
+ * An interference key.
+ *
+ * @param id The identifier of the member.
+ * @param groups The groups to which the key belongs.
+ */
+ private inner class InterferenceKeyImpl(val id: String, private val groups: List<GroupContext>) : InterferenceKey {
+ init {
+ for (group in groups) {
+ group.join(this)
+ }
+ }
+
+ /**
+ * Find the active group that applies for the interference member.
+ */
+ fun findGroup(load: Double): GroupContext? {
+ // Find the first active group whose target load is lower than the current load
+ val index = groups.binarySearchBy(load) { it.group.targetLoad }
+ val target = if (index >= 0) index else -(index + 1)
+
+ // Check whether there are active groups ahead of the index
+ for (i in target until groups.size) {
+ val group = groups[i]
+ if (group.group.targetLoad > load) {
+ break
+ } else if (group.isActive) {
+ return group
+ }
+ }
+
+ // Check whether there are active groups before the index
+ for (i in (target - 1) downTo 0) {
+ val group = groups[i]
+ if (group.isActive) {
+ return group
+ }
+ }
+
+ return null
+ }
+
+ /**
+ * Leave all the groups.
+ */
+ fun leave() {
+ for (group in groups) {
+ group.leave(this)
+ }
+ }
+ }
+
+ /**
+ * A group context is used to track the active keys per interference group.
+ */
+ private inner class GroupContext(val group: VmInterferenceGroup) {
+ /**
+ * The active keys that are part of this group.
+ */
+ private val keys = mutableSetOf<InterferenceKeyImpl>()
+
+ /**
+ * A flag to indicate that the group is active.
+ */
+ val isActive
+ get() = keys.size > 1
+
+ /**
+ * Determine whether the specified [id] is part of this group.
+ */
+ operator fun contains(id: String): Boolean = id in group.members
+
+ /**
+ * Join this group with the specified [key].
+ */
+ fun join(key: InterferenceKeyImpl) {
+ keys += key
+ }
+
+ /**
+ * Leave this group with the specified [key].
+ */
+ fun leave(key: InterferenceKeyImpl) {
+ keys -= key
+ }
+ }
+}
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/model/MachineModel.kt
index 2b414540..7e4d7191 100644
--- 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/model/MachineModel.kt
@@ -20,15 +20,19 @@
* SOFTWARE.
*/
-package org.opendc.simulator.compute
-
-import org.opendc.simulator.compute.model.MemoryUnit
-import org.opendc.simulator.compute.model.ProcessingUnit
+package org.opendc.simulator.compute.model
/**
* 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.
+ * @property net A list of network adapters available to the machine.
+ * @property storage A list of storage devices available to the machine.
*/
-public data class SimMachineModel(public val cpus: List<ProcessingUnit>, public val memory: List<MemoryUnit>)
+public data class MachineModel(
+ public val cpus: List<ProcessingUnit>,
+ public val memory: List<MemoryUnit>,
+ public val net: List<NetworkAdapter> = emptyList(),
+ public val storage: List<StorageDevice> = emptyList()
+)
diff --git a/opendc-simulator/opendc-simulator-compute/src/main/kotlin/org/opendc/simulator/compute/model/NetworkAdapter.kt b/opendc-simulator/opendc-simulator-compute/src/main/kotlin/org/opendc/simulator/compute/model/NetworkAdapter.kt
new file mode 100644
index 00000000..46472144
--- /dev/null
+++ b/opendc-simulator/opendc-simulator-compute/src/main/kotlin/org/opendc/simulator/compute/model/NetworkAdapter.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.model
+
+/**
+ * A description of a network adapter that is
+ *
+ * @property vendor The vendor of the network adapter.
+ * @property modelName The model name of the network adapter.
+ * @property bandwidth The bandwidth of the network adapter in Mbps.
+ */
+public data class NetworkAdapter(
+ public val vendor: String,
+ public val modelName: String,
+ public val bandwidth: Double
+)
diff --git a/opendc-simulator/opendc-simulator-compute/src/main/kotlin/org/opendc/simulator/compute/model/StorageDevice.kt b/opendc-simulator/opendc-simulator-compute/src/main/kotlin/org/opendc/simulator/compute/model/StorageDevice.kt
new file mode 100644
index 00000000..2621ad6d
--- /dev/null
+++ b/opendc-simulator/opendc-simulator-compute/src/main/kotlin/org/opendc/simulator/compute/model/StorageDevice.kt
@@ -0,0 +1,40 @@
+/*
+ * 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.model
+
+/**
+ * Model for a physical storage device attached to a machine.
+ *
+ * @property vendor The vendor of the storage device.
+ * @property modelName The model name of the device.
+ * @property capacity The capacity of the device.
+ * @property readBandwidth The read bandwidth of the device in MBps.
+ * @property writeBandwidth The write bandwidth of the device in MBps.
+ */
+public data class StorageDevice(
+ public val vendor: String,
+ public val modelName: String,
+ public val capacity: Double,
+ public val readBandwidth: Double,
+ public val writeBandwidth: 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
index ddc6d5b1..62b85e12 100644
--- 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
@@ -1,3 +1,25 @@
+/*
+ * 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.power
import kotlin.math.E
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
index b8cb8412..0fe32b0d 100644
--- 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
@@ -1,3 +1,25 @@
+/*
+ * 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.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
index 9c44438a..0d3bf6cc 100644
--- 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
@@ -1,3 +1,25 @@
+/*
+ * 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.power
import kotlin.math.pow
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
index cbfcd810..2694700c 100644
--- 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
@@ -1,6 +1,27 @@
+/*
+ * 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.power
-import org.yaml.snakeyaml.Yaml
import kotlin.math.ceil
import kotlin.math.floor
import kotlin.math.max
@@ -15,8 +36,6 @@ import kotlin.math.min
* @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()
@@ -41,14 +60,4 @@ public class InterpolationPowerModel(private val powerValues: List<Double>) : Po
* @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
index 0f45afae..dadc56ec 100644
--- 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
@@ -1,3 +1,25 @@
+/*
+ * 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.power
/**
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
index 8486d680..612ce2fc 100644
--- 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
@@ -1,3 +1,25 @@
+/*
+ * 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.power
import kotlin.math.pow
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..f71446f8 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
- }
+ private val states: TreeMap<Double, PowerModel> = TreeMap(states)
+ 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.rate
}
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[states=$states]"
}
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..1a46dd4a 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,33 +20,28 @@
* 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 tracking the power usage for a component of the machine.
*/
-public interface ScalingDriver {
+public interface PowerDriver {
/**
- * Create the scaling logic for the specified [machine]
+ * Create the driver 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.
+ * The logic of the power 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.
+ * Compute the power consumption of the component.
*
- * @return The power consumption of the processor in W.
+ * @return The power consumption of the component in W.
*/
public fun computePower(): Double
}
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
index 1387e65a..decb2420 100644
--- 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
@@ -1,3 +1,25 @@
+/*
+ * 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.power
import org.opendc.simulator.compute.SimMachine
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/power/SimplePowerDriver.kt
index cf0bbb28..34e91c35 100644
--- 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/power/SimplePowerDriver.kt
@@ -20,30 +20,29 @@
* 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
/**
- * A [ScalingDriver] that ignores the instructions of the [ScalingGovernor] and directly computes the power consumption
- * based on the specified [power model][model].
+ * A [PowerDriver] that computes the power consumption based on a single 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
+public class SimplePowerDriver(private val model: PowerModel) : PowerDriver {
+ override fun createLogic(machine: SimMachine, cpus: List<SimProcessingUnit>): PowerDriver.Logic = object : PowerDriver.Logic {
- override val cpu: SimProcessingUnit = cpu
+ override fun computePower(): Double {
+ var targetFreq = 0.0
+ var totalSpeed = 0.0
- override fun setTarget(freq: Double) {}
+ for (cpu in cpus) {
+ targetFreq += cpu.capacity
+ totalSpeed += cpu.rate
}
- }
-
- override fun computePower(): Double = model.computePower(machine.usage.value)
- override fun toString(): String = "SimpleScalingDriver.Logic"
+ return model.computePower(totalSpeed / targetFreq)
+ }
}
+
+ override fun toString(): String = "SimplePowerDriver[model=$model]"
}
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
index afa1d82f..0665dbd9 100644
--- 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
@@ -1,3 +1,25 @@
+/*
+ * 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.power
import kotlin.math.sqrt
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
index 82a9d37d..e4ae88a9 100644
--- 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
@@ -1,3 +1,25 @@
+/*
+ * 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.power
import kotlin.math.pow
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
index 19dfcadd..886227e1 100644
--- 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
@@ -1,3 +1,25 @@
+/*
+ * 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.power
/**
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..99f4a1e1 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,9 +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.resources.consumer.SimWorkConsumer
+import org.opendc.simulator.flow.source.FixedFlowSource
/**
* A [SimWorkload] that models applications as a static number of floating point operations ([flops]) executed on
@@ -43,10 +41,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(FixedFlowSource(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..2ef3bc43 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,9 +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.resources.consumer.SimWorkConsumer
+import org.opendc.simulator.flow.source.FixedFlowSource
/**
* A [SimWorkload] that models application execution as a single duration.
@@ -42,11 +40,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(FixedFlowSource((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/SimTrace.kt b/opendc-simulator/opendc-simulator-compute/src/main/kotlin/org/opendc/simulator/compute/workload/SimTrace.kt
new file mode 100644
index 00000000..4f567b55
--- /dev/null
+++ b/opendc-simulator/opendc-simulator-compute/src/main/kotlin/org/opendc/simulator/compute/workload/SimTrace.kt
@@ -0,0 +1,233 @@
+/*
+ * 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.workload
+
+import org.opendc.simulator.compute.model.ProcessingUnit
+import org.opendc.simulator.flow.FlowConnection
+import org.opendc.simulator.flow.FlowSource
+import kotlin.math.min
+
+/**
+ * A workload trace that describes the resource utilization over time in a collection of [SimTraceFragment]s.
+ *
+ * @param usageCol The column containing the CPU usage of each fragment (in MHz).
+ * @param timestampCol The column containing the starting timestamp for each fragment (in epoch millis).
+ * @param deadlineCol The column containing the ending timestamp for each fragment (in epoch millis).
+ * @param coresCol The column containing the utilized cores.
+ * @param size The number of fragments in the trace.
+ */
+public class SimTrace(
+ private val usageCol: DoubleArray,
+ private val timestampCol: LongArray,
+ private val deadlineCol: LongArray,
+ private val coresCol: IntArray,
+ private val size: Int,
+) {
+ init {
+ require(size >= 0) { "Invalid trace size" }
+ require(usageCol.size >= size) { "Invalid number of usage entries" }
+ require(timestampCol.size >= size) { "Invalid number of timestamp entries" }
+ require(deadlineCol.size >= size) { "Invalid number of deadline entries" }
+ require(coresCol.size >= size) { "Invalid number of core entries" }
+ }
+
+ public companion object {
+ /**
+ * Construct a [SimTrace] with the specified fragments.
+ */
+ public fun ofFragments(fragments: List<SimTraceFragment>): SimTrace {
+ val size = fragments.size
+ val usageCol = DoubleArray(size)
+ val timestampCol = LongArray(size)
+ val deadlineCol = LongArray(size)
+ val coresCol = IntArray(size)
+
+ for (i in fragments.indices) {
+ val fragment = fragments[i]
+ usageCol[i] = fragment.usage
+ timestampCol[i] = fragment.timestamp
+ deadlineCol[i] = fragment.timestamp + fragment.duration
+ coresCol[i] = fragment.cores
+ }
+
+ return SimTrace(usageCol, timestampCol, deadlineCol, coresCol, size)
+ }
+
+ /**
+ * Construct a [SimTrace] with the specified fragments.
+ */
+ @JvmStatic
+ public fun ofFragments(vararg fragments: SimTraceFragment): SimTrace {
+ val size = fragments.size
+ val usageCol = DoubleArray(size)
+ val timestampCol = LongArray(size)
+ val deadlineCol = LongArray(size)
+ val coresCol = IntArray(size)
+
+ for (i in fragments.indices) {
+ val fragment = fragments[i]
+ usageCol[i] = fragment.usage
+ timestampCol[i] = fragment.timestamp
+ deadlineCol[i] = fragment.timestamp + fragment.duration
+ coresCol[i] = fragment.cores
+ }
+
+ return SimTrace(usageCol, timestampCol, deadlineCol, coresCol, size)
+ }
+
+ /**
+ * Create a [SimTrace.Builder] instance.
+ */
+ @JvmStatic
+ public fun builder(): Builder = Builder()
+ }
+
+ /**
+ * Construct a new [FlowSource] for the specified [cpu].
+ */
+ public fun newSource(cpu: ProcessingUnit, offset: Long): FlowSource {
+ return CpuConsumer(cpu, offset, usageCol, timestampCol, deadlineCol, coresCol, size)
+ }
+
+ /**
+ * A builder class for a [SimTrace].
+ */
+ public class Builder internal constructor() {
+ /**
+ * The columns of the trace.
+ */
+ private var usageCol: DoubleArray = DoubleArray(16)
+ private var timestampCol: LongArray = LongArray(16)
+ private var deadlineCol: LongArray = LongArray(16)
+ private var coresCol: IntArray = IntArray(16)
+
+ /**
+ * The number of entries in the trace.
+ */
+ private var size = 0
+
+ /**
+ * Add the specified [SimTraceFragment] to the trace.
+ */
+ public fun add(fragment: SimTraceFragment) {
+ add(fragment.timestamp, fragment.timestamp + fragment.duration, fragment.usage, fragment.cores)
+ }
+
+ /**
+ * Add a fragment to the trace.
+ *
+ * @param timestamp Timestamp at which the fragment starts (in epoch millis).
+ * @param deadline Timestamp at which the fragment ends (in epoch millis).
+ * @param usage CPU usage of this fragment.
+ * @param cores Number of cores used.
+ */
+ public fun add(timestamp: Long, deadline: Long, usage: Double, cores: Int) {
+ val size = size
+
+ if (size == usageCol.size) {
+ grow()
+ }
+
+ timestampCol[size] = timestamp
+ deadlineCol[size] = deadline
+ usageCol[size] = usage
+ coresCol[size] = cores
+
+ this.size++
+ }
+
+ /**
+ * Helper function to grow the capacity of the column arrays.
+ */
+ private fun grow() {
+ val arraySize = usageCol.size
+ val newSize = arraySize * 2
+
+ usageCol = usageCol.copyOf(newSize)
+ timestampCol = timestampCol.copyOf(newSize)
+ deadlineCol = deadlineCol.copyOf(newSize)
+ coresCol = coresCol.copyOf(newSize)
+ }
+
+ /**
+ * Construct the immutable [SimTrace].
+ */
+ public fun build(): SimTrace {
+ return SimTrace(usageCol, timestampCol, deadlineCol, coresCol, size)
+ }
+ }
+
+ /**
+ * A CPU consumer for the trace workload.
+ */
+ private class CpuConsumer(
+ cpu: ProcessingUnit,
+ private val offset: Long,
+ private val usageCol: DoubleArray,
+ private val timestampCol: LongArray,
+ private val deadlineCol: LongArray,
+ private val coresCol: IntArray,
+ private val size: Int
+ ) : FlowSource {
+ private val id = cpu.id
+ private val coreCount = cpu.node.coreCount
+
+ /**
+ * The index in the trace.
+ */
+ private var _idx = 0
+
+ override fun onPull(conn: FlowConnection, now: Long, delta: Long): Long {
+ val size = size
+ val nowOffset = now - offset
+
+ var idx = _idx
+ val deadlines = deadlineCol
+ var deadline = deadlines[idx]
+
+ while (deadline <= nowOffset && ++idx < size) {
+ deadline = deadlines[idx]
+ }
+
+ if (idx >= size) {
+ conn.close()
+ return Long.MAX_VALUE
+ }
+
+ _idx = idx
+ val timestamp = timestampCol[idx]
+
+ // Fragment is in the future
+ if (timestamp > nowOffset) {
+ conn.push(0.0)
+ return timestamp - nowOffset
+ }
+
+ val cores = min(coreCount, coresCol[idx])
+ val usage = usageCol[idx]
+
+ conn.push(if (id < cores) usage / cores else 0.0)
+ return deadline - nowOffset
+ }
+ }
+}
diff --git a/opendc-simulator/opendc-simulator-compute/src/main/kotlin/org/opendc/simulator/compute/workload/SimTraceFragment.kt b/opendc-simulator/opendc-simulator-compute/src/main/kotlin/org/opendc/simulator/compute/workload/SimTraceFragment.kt
new file mode 100644
index 00000000..5285847f
--- /dev/null
+++ b/opendc-simulator/opendc-simulator-compute/src/main/kotlin/org/opendc/simulator/compute/workload/SimTraceFragment.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.workload
+
+/**
+ * A fragment of the workload trace.
+ *
+ * @param timestamp The timestamp at which the fragment starts (in epoch millis).
+ * @param duration The duration of the fragment (in milliseconds).
+ * @param usage The CPU usage during the fragment (in MHz).
+ * @param cores The amount of cores utilized during the fragment.
+ */
+public data class SimTraceFragment(
+ @JvmField val timestamp: Long,
+ @JvmField val duration: Long,
+ @JvmField val usage: Double,
+ @JvmField val cores: Int
+)
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..53c98409 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
@@ -23,72 +23,22 @@
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.
+ *
+ * @param trace The trace of fragments to use.
+ * @param offset The offset for the timestamps.
*/
-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
-
+public class SimTraceWorkload(private val trace: SimTrace, private val offset: Long = 0L) : SimWorkload {
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 lifecycle = SimWorkloadLifecycle(ctx)
- 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
- }
+ for (cpu in ctx.cpus) {
+ cpu.startConsumer(lifecycle.waitFor(trace.newSource(cpu.model, offset)))
}
}
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
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/main/kotlin/org/opendc/simulator/compute/workload/SimWorkloadLifecycle.kt b/opendc-simulator/opendc-simulator-compute/src/main/kotlin/org/opendc/simulator/compute/workload/SimWorkloadLifecycle.kt
new file mode 100644
index 00000000..cc4f1f6a
--- /dev/null
+++ b/opendc-simulator/opendc-simulator-compute/src/main/kotlin/org/opendc/simulator/compute/workload/SimWorkloadLifecycle.kt
@@ -0,0 +1,63 @@
+/*
+ * 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.workload
+
+import org.opendc.simulator.compute.SimMachineContext
+import org.opendc.simulator.flow.FlowConnection
+import org.opendc.simulator.flow.FlowSource
+
+/**
+ * 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<FlowSource>()
+
+ /**
+ * Wait for the specified [consumer] to complete before ending the lifecycle of the workload.
+ */
+ public fun waitFor(consumer: FlowSource): FlowSource {
+ waiting.add(consumer)
+ return object : FlowSource by consumer {
+ override fun onStop(conn: FlowConnection, now: Long, delta: Long) {
+ try {
+ consumer.onStop(conn, now, delta)
+ } finally {
+ complete(consumer)
+ }
+ }
+ override fun toString(): String = "SimWorkloadLifecycle.Consumer[delegate=$consumer]"
+ }
+ }
+
+ /**
+ * Complete the specified [FlowSource].
+ */
+ private fun complete(consumer: FlowSource) {
+ if (waiting.remove(consumer) && waiting.isEmpty()) {
+ ctx.close()
+ }
+ }
+}
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
deleted file mode 100644
index 8886caa7..00000000
--- a/opendc-simulator/opendc-simulator-compute/src/test/kotlin/org/opendc/simulator/compute/SimHypervisorTest.kt
+++ /dev/null
@@ -1,199 +0,0 @@
-/*
- * 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
-import org.opendc.simulator.resources.SimResourceSchedulerTrampoline
-
-/**
- * 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(SimResourceSchedulerTrampoline(coroutineContext, clock), 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(SimResourceSchedulerTrampoline(coroutineContext, clock), 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
index 205f2eca..0bb24ed8 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
@@ -23,41 +23,47 @@
package org.opendc.simulator.compute
import kotlinx.coroutines.*
-import kotlinx.coroutines.flow.toList
+import org.junit.jupiter.api.*
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.device.SimNetworkAdapter
+import org.opendc.simulator.compute.model.*
import org.opendc.simulator.compute.power.ConstantPowerModel
+import org.opendc.simulator.compute.power.LinearPowerModel
+import org.opendc.simulator.compute.power.SimplePowerDriver
import org.opendc.simulator.compute.workload.SimFlopsWorkload
+import org.opendc.simulator.compute.workload.SimWorkload
+import org.opendc.simulator.compute.workload.SimWorkloadLifecycle
import org.opendc.simulator.core.runBlockingSimulation
+import org.opendc.simulator.flow.FlowEngine
+import org.opendc.simulator.flow.source.FixedFlowSource
+import org.opendc.simulator.network.SimNetworkSink
+import org.opendc.simulator.power.SimPowerSource
/**
* Test suite for the [SimBareMetalMachine] class.
*/
-@OptIn(ExperimentalCoroutinesApi::class)
class SimMachineTest {
- private lateinit var machineModel: SimMachineModel
+ private lateinit var machineModel: MachineModel
@BeforeEach
fun setUp() {
val cpuNode = ProcessingNode("Intel", "Xeon", "amd64", 2)
- machineModel = SimMachineModel(
+ machineModel = MachineModel(
cpus = List(cpuNode.coreCount) { ProcessingUnit(cpuNode, it, 1000.0) },
- memory = List(4) { MemoryUnit("Crucial", "MTA18ASF4G72AZ-3G2B1", 3200.0, 32_000) }
+ memory = List(4) { MemoryUnit("Crucial", "MTA18ASF4G72AZ-3G2B1", 3200.0, 32_000) },
+ net = listOf(NetworkAdapter("Mellanox", "ConnectX-5", 25000.0)),
+ storage = listOf(StorageDevice("Samsung", "EVO", 1000.0, 250.0, 250.0))
)
}
@Test
fun testFlopsWorkload() = runBlockingSimulation {
- val machine = SimBareMetalMachine(coroutineContext, clock, machineModel, PerformanceScalingGovernor(), SimpleScalingDriver(ConstantPowerModel(0.0)))
+ val machine = SimBareMetalMachine(
+ FlowEngine(coroutineContext, clock),
+ machineModel,
+ SimplePowerDriver(ConstantPowerModel(0.0))
+ )
try {
machine.run(SimFlopsWorkload(2_000, utilization = 1.0))
@@ -72,11 +78,15 @@ class SimMachineTest {
@Test
fun testDualSocketMachine() = runBlockingSimulation {
val cpuNode = machineModel.cpus[0].node
- val machineModel = SimMachineModel(
+ val machineModel = MachineModel(
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(
+ FlowEngine(coroutineContext, clock),
+ machineModel,
+ SimplePowerDriver(ConstantPowerModel(0.0))
+ )
try {
machine.run(SimFlopsWorkload(2_000, utilization = 1.0))
@@ -89,17 +99,212 @@ class SimMachineTest {
}
@Test
- fun testUsage() = runBlockingSimulation {
- val machine = SimBareMetalMachine(coroutineContext, clock, machineModel, PerformanceScalingGovernor(), SimpleScalingDriver(ConstantPowerModel(0.0)))
+ fun testPower() = runBlockingSimulation {
+ val engine = FlowEngine(coroutineContext, clock)
+ val machine = SimBareMetalMachine(
+ engine,
+ machineModel,
+ SimplePowerDriver(LinearPowerModel(100.0, 50.0))
+ )
+ val source = SimPowerSource(engine, capacity = 1000.0)
+ source.connect(machine.psu)
- val res = mutableListOf<Double>()
- val job = launch { machine.usage.toList(res) }
+ try {
+ coroutineScope {
+ launch { machine.run(SimFlopsWorkload(2_000, utilization = 1.0)) }
+ assertAll(
+ { assertEquals(100.0, machine.psu.powerDraw) },
+ { assertEquals(100.0, source.powerDraw) }
+ )
+ }
+ } finally {
+ machine.close()
+ }
+ }
+
+ @Test
+ fun testCapacityClamp() = runBlockingSimulation {
+ val machine = SimBareMetalMachine(
+ FlowEngine(coroutineContext, clock),
+ machineModel,
+ SimplePowerDriver(ConstantPowerModel(0.0))
+ )
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" }
+ machine.run(object : SimWorkload {
+ override fun onStart(ctx: SimMachineContext) {
+ val cpu = ctx.cpus[0]
+
+ cpu.capacity = cpu.model.frequency + 1000.0
+ assertEquals(cpu.model.frequency, cpu.capacity)
+ cpu.capacity = -1.0
+ assertEquals(0.0, cpu.capacity)
+
+ ctx.close()
+ }
+ })
+ } finally {
+ machine.close()
+ }
+ }
+
+ @Test
+ fun testMemory() = runBlockingSimulation {
+ val machine = SimBareMetalMachine(
+ FlowEngine(coroutineContext, clock),
+ machineModel,
+ SimplePowerDriver(ConstantPowerModel(0.0))
+ )
+
+ try {
+ machine.run(object : SimWorkload {
+ override fun onStart(ctx: SimMachineContext) {
+ assertEquals(32_000 * 4.0, ctx.memory.capacity)
+ ctx.close()
+ }
+ })
+ } finally {
+ machine.close()
+ }
+ }
+
+ @Test
+ fun testMemoryUsage() = runBlockingSimulation {
+ val machine = SimBareMetalMachine(
+ FlowEngine(coroutineContext, clock),
+ machineModel,
+ SimplePowerDriver(ConstantPowerModel(0.0))
+ )
+
+ try {
+ machine.run(object : SimWorkload {
+ override fun onStart(ctx: SimMachineContext) {
+ val lifecycle = SimWorkloadLifecycle(ctx)
+ ctx.memory.startConsumer(lifecycle.waitFor(FixedFlowSource(ctx.memory.capacity, utilization = 0.8)))
+ }
+ })
+
+ assertEquals(1250, clock.millis())
+ } finally {
+ machine.close()
+ }
+ }
+
+ @Test
+ fun testNetUsage() = runBlockingSimulation {
+ val engine = FlowEngine(coroutineContext, clock)
+ val machine = SimBareMetalMachine(
+ engine,
+ machineModel,
+ SimplePowerDriver(ConstantPowerModel(0.0))
+ )
+
+ val adapter = (machine.peripherals[0] as SimNetworkAdapter)
+ adapter.connect(SimNetworkSink(engine, adapter.bandwidth))
+
+ try {
+ machine.run(object : SimWorkload {
+ override fun onStart(ctx: SimMachineContext) {
+ val lifecycle = SimWorkloadLifecycle(ctx)
+ val iface = ctx.net[0]
+ iface.tx.startConsumer(lifecycle.waitFor(FixedFlowSource(iface.bandwidth, utilization = 0.8)))
+ }
+ })
+
+ assertEquals(1250, clock.millis())
+ } finally {
+ machine.close()
+ }
+ }
+
+ @Test
+ fun testDiskReadUsage() = runBlockingSimulation {
+ val engine = FlowEngine(coroutineContext, clock)
+ val machine = SimBareMetalMachine(
+ engine,
+ machineModel,
+ SimplePowerDriver(ConstantPowerModel(0.0))
+ )
+
+ try {
+ machine.run(object : SimWorkload {
+ override fun onStart(ctx: SimMachineContext) {
+ val lifecycle = SimWorkloadLifecycle(ctx)
+ val disk = ctx.storage[0]
+ disk.read.startConsumer(lifecycle.waitFor(FixedFlowSource(disk.read.capacity, utilization = 0.8)))
+ }
+ })
+
+ assertEquals(1250, clock.millis())
+ } finally {
+ machine.close()
+ }
+ }
+
+ @Test
+ fun testDiskWriteUsage() = runBlockingSimulation {
+ val engine = FlowEngine(coroutineContext, clock)
+ val machine = SimBareMetalMachine(
+ engine,
+ machineModel,
+ SimplePowerDriver(ConstantPowerModel(0.0))
+ )
+
+ try {
+ machine.run(object : SimWorkload {
+ override fun onStart(ctx: SimMachineContext) {
+ val lifecycle = SimWorkloadLifecycle(ctx)
+ val disk = ctx.storage[0]
+ disk.write.startConsumer(lifecycle.waitFor(FixedFlowSource(disk.write.capacity, utilization = 0.8)))
+ }
+ })
+
+ assertEquals(1250, clock.millis())
+ } finally {
+ machine.close()
+ }
+ }
+
+ @Test
+ fun testCancellation() = runBlockingSimulation {
+ val machine = SimBareMetalMachine(
+ FlowEngine(coroutineContext, clock),
+ machineModel,
+ SimplePowerDriver(ConstantPowerModel(0.0))
+ )
+
+ try {
+ coroutineScope {
+ launch { machine.run(SimFlopsWorkload(2_000, utilization = 1.0)) }
+ cancel()
+ }
+ } catch (_: CancellationException) {
+ // Ignore
+ } finally {
+ machine.close()
+ }
+
+ assertEquals(0, clock.millis())
+ }
+
+ @Test
+ fun testConcurrentRuns() = runBlockingSimulation {
+ val machine = SimBareMetalMachine(
+ FlowEngine(coroutineContext, clock),
+ machineModel,
+ SimplePowerDriver(ConstantPowerModel(0.0))
+ )
+
+ try {
+ coroutineScope {
+ launch {
+ machine.run(SimFlopsWorkload(2_000, utilization = 1.0))
+ }
+
+ assertThrows<IllegalStateException> {
+ machine.run(SimFlopsWorkload(2_000, utilization = 1.0))
+ }
+ }
} finally {
machine.close()
}
@@ -107,7 +312,11 @@ class SimMachineTest {
@Test
fun testClose() = runBlockingSimulation {
- val machine = SimBareMetalMachine(coroutineContext, clock, machineModel, PerformanceScalingGovernor(), SimpleScalingDriver(ConstantPowerModel(0.0)))
+ val machine = SimBareMetalMachine(
+ FlowEngine(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/device/SimPsuTest.kt b/opendc-simulator/opendc-simulator-compute/src/test/kotlin/org/opendc/simulator/compute/device/SimPsuTest.kt
new file mode 100644
index 00000000..e5b509f0
--- /dev/null
+++ b/opendc-simulator/opendc-simulator-compute/src/test/kotlin/org/opendc/simulator/compute/device/SimPsuTest.kt
@@ -0,0 +1,102 @@
+/*
+ * 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.device
+
+import io.mockk.every
+import io.mockk.mockk
+import org.junit.jupiter.api.Assertions.assertEquals
+import org.junit.jupiter.api.Test
+import org.junit.jupiter.api.assertThrows
+import org.opendc.simulator.compute.power.PowerDriver
+import org.opendc.simulator.core.runBlockingSimulation
+import org.opendc.simulator.flow.FlowEngine
+import org.opendc.simulator.power.SimPowerSource
+
+/**
+ * Test suite for [SimPsu]
+ */
+internal class SimPsuTest {
+
+ @Test
+ fun testInvalidInput() {
+ assertThrows<IllegalArgumentException> { SimPsu(1.0, emptyMap()) }
+ }
+
+ @Test
+ fun testDoubleConnect() {
+ val psu = SimPsu(1.0, mapOf(0.0 to 1.0))
+ val cpuLogic = mockk<PowerDriver.Logic>()
+ psu.connect(cpuLogic)
+ assertThrows<IllegalStateException> { psu.connect(mockk()) }
+ }
+
+ @Test
+ fun testPsuIdle() = runBlockingSimulation {
+ val ratedOutputPower = 240.0
+ val energyEfficiency = mapOf(0.0 to 1.0)
+
+ val engine = FlowEngine(coroutineContext, clock)
+ val source = SimPowerSource(engine, capacity = ratedOutputPower)
+
+ val cpuLogic = mockk<PowerDriver.Logic>()
+ every { cpuLogic.computePower() } returns 0.0
+
+ val psu = SimPsu(ratedOutputPower, energyEfficiency)
+ psu.connect(cpuLogic)
+ source.connect(psu)
+
+ assertEquals(0.0, source.powerDraw, 0.01)
+ }
+
+ @Test
+ fun testPsuPowerLoss() = runBlockingSimulation {
+ val ratedOutputPower = 240.0
+ // Efficiency of 80 Plus Titanium PSU
+ val energyEfficiency = sortedMapOf(
+ 0.3 to 0.9,
+ 0.7 to 0.92,
+ 1.0 to 0.94,
+ )
+
+ val engine = FlowEngine(coroutineContext, clock)
+ val source = SimPowerSource(engine, capacity = ratedOutputPower)
+
+ val cpuLogic = mockk<PowerDriver.Logic>()
+ every { cpuLogic.computePower() } returnsMany listOf(50.0, 100.0, 150.0, 200.0)
+
+ val psu = SimPsu(ratedOutputPower, energyEfficiency)
+ psu.connect(cpuLogic)
+ source.connect(psu)
+
+ assertEquals(55.55, source.powerDraw, 0.01)
+
+ psu.update()
+ assertEquals(108.695, source.powerDraw, 0.01)
+
+ psu.update()
+ assertEquals(163.043, source.powerDraw, 0.01)
+
+ psu.update()
+ assertEquals(212.765, source.powerDraw, 0.01)
+ }
+}
diff --git a/opendc-simulator/opendc-simulator-compute/src/test/kotlin/org/opendc/simulator/compute/kernel/SimFairShareHypervisorTest.kt b/opendc-simulator/opendc-simulator-compute/src/test/kotlin/org/opendc/simulator/compute/kernel/SimFairShareHypervisorTest.kt
new file mode 100644
index 00000000..6f32cf46
--- /dev/null
+++ b/opendc-simulator/opendc-simulator-compute/src/test/kotlin/org/opendc/simulator/compute/kernel/SimFairShareHypervisorTest.kt
@@ -0,0 +1,240 @@
+/*
+ * 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.kernel
+
+import kotlinx.coroutines.*
+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.SimBareMetalMachine
+import org.opendc.simulator.compute.kernel.cpufreq.PerformanceScalingGovernor
+import org.opendc.simulator.compute.kernel.interference.VmInterferenceGroup
+import org.opendc.simulator.compute.kernel.interference.VmInterferenceModel
+import org.opendc.simulator.compute.model.MachineModel
+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.SimTrace
+import org.opendc.simulator.compute.workload.SimTraceFragment
+import org.opendc.simulator.compute.workload.SimTraceWorkload
+import org.opendc.simulator.core.runBlockingSimulation
+import org.opendc.simulator.flow.FlowEngine
+
+/**
+ * Test suite for the [SimHypervisor] class.
+ */
+@OptIn(ExperimentalCoroutinesApi::class)
+internal class SimFairShareHypervisorTest {
+ private lateinit var model: MachineModel
+
+ @BeforeEach
+ fun setUp() {
+ val cpuNode = ProcessingNode("Intel", "Xeon", "amd64", 1)
+ model = MachineModel(
+ 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 duration = 5 * 60L
+ val workloadA =
+ SimTraceWorkload(
+ SimTrace.ofFragments(
+ SimTraceFragment(0, duration * 1000, 28.0, 1),
+ SimTraceFragment(duration * 1000, duration * 1000, 3500.0, 1),
+ SimTraceFragment(duration * 2000, duration * 1000, 0.0, 1),
+ SimTraceFragment(duration * 3000, duration * 1000, 183.0, 1)
+ ),
+ )
+
+ val platform = FlowEngine(coroutineContext, clock)
+ val machine = SimBareMetalMachine(platform, model, SimplePowerDriver(ConstantPowerModel(0.0)))
+ val hypervisor = SimFairShareHypervisor(platform, null, PerformanceScalingGovernor(), null)
+
+ launch {
+ machine.run(hypervisor)
+ println("Hypervisor finished")
+ }
+ yield()
+
+ val vm = hypervisor.createMachine(model)
+ vm.run(workloadA)
+
+ yield()
+ machine.close()
+
+ assertAll(
+ { assertEquals(319781, hypervisor.counters.cpuActiveTime, "Active time does not match") },
+ { assertEquals(880219, hypervisor.counters.cpuIdleTime, "Idle time does not match") },
+ { assertEquals(28125, hypervisor.counters.cpuStealTime, "Steal time does not match") },
+ { assertEquals(1200000, clock.millis()) { "Current time is correct" } }
+ )
+ }
+
+ /**
+ * Test overcommitting of resources via the hypervisor with two VMs.
+ */
+ @Test
+ fun testOvercommittedDual() = runBlockingSimulation {
+ val duration = 5 * 60L
+ val workloadA =
+ SimTraceWorkload(
+ SimTrace.ofFragments(
+ SimTraceFragment(0, duration * 1000, 28.0, 1),
+ SimTraceFragment(duration * 1000, duration * 1000, 3500.0, 1),
+ SimTraceFragment(duration * 2000, duration * 1000, 0.0, 1),
+ SimTraceFragment(duration * 3000, duration * 1000, 183.0, 1)
+ ),
+ )
+ val workloadB =
+ SimTraceWorkload(
+ SimTrace.ofFragments(
+ SimTraceFragment(0, duration * 1000, 28.0, 1),
+ SimTraceFragment(duration * 1000, duration * 1000, 3100.0, 1),
+ SimTraceFragment(duration * 2000, duration * 1000, 0.0, 1),
+ SimTraceFragment(duration * 3000, duration * 1000, 73.0, 1)
+ )
+ )
+
+ val platform = FlowEngine(coroutineContext, clock)
+ val machine = SimBareMetalMachine(
+ platform, model, SimplePowerDriver(ConstantPowerModel(0.0))
+ )
+ val hypervisor = SimFairShareHypervisor(platform, null, null, null)
+
+ 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(329250, hypervisor.counters.cpuActiveTime, "Active time does not match") },
+ { assertEquals(870750, hypervisor.counters.cpuIdleTime, "Idle time does not match") },
+ { assertEquals(318750, hypervisor.counters.cpuStealTime, "Steal time does not match") },
+ { assertEquals(1200000, clock.millis()) }
+ )
+ }
+
+ @Test
+ fun testMultipleCPUs() = runBlockingSimulation {
+ val cpuNode = ProcessingNode("Intel", "Xeon", "amd64", 2)
+ val model = MachineModel(
+ cpus = List(cpuNode.coreCount) { ProcessingUnit(cpuNode, it, 3200.0) },
+ memory = List(4) { MemoryUnit("Crucial", "MTA18ASF4G72AZ-3G2B1", 3200.0, 32_000) }
+ )
+
+ val platform = FlowEngine(coroutineContext, clock)
+ val machine = SimBareMetalMachine(platform, model, SimplePowerDriver(ConstantPowerModel(0.0)))
+ val hypervisor = SimFairShareHypervisor(platform, null, null, null)
+
+ assertDoesNotThrow {
+ launch {
+ machine.run(hypervisor)
+ }
+ }
+
+ machine.close()
+ }
+
+ @Test
+ fun testInterference() = runBlockingSimulation {
+ val cpuNode = ProcessingNode("Intel", "Xeon", "amd64", 2)
+ val model = MachineModel(
+ cpus = List(cpuNode.coreCount) { ProcessingUnit(cpuNode, it, 3200.0) },
+ memory = List(4) { MemoryUnit("Crucial", "MTA18ASF4G72AZ-3G2B1", 3200.0, 32_000) }
+ )
+
+ val groups = listOf(
+ VmInterferenceGroup(targetLoad = 0.0, score = 0.9, members = setOf("a", "b")),
+ VmInterferenceGroup(targetLoad = 0.0, score = 0.6, members = setOf("a", "c")),
+ VmInterferenceGroup(targetLoad = 0.1, score = 0.8, members = setOf("a", "n"))
+ )
+ val interferenceModel = VmInterferenceModel(groups)
+
+ val platform = FlowEngine(coroutineContext, clock)
+ val machine = SimBareMetalMachine(
+ platform, model, SimplePowerDriver(ConstantPowerModel(0.0))
+ )
+ val hypervisor = SimFairShareHypervisor(platform, null, null, interferenceModel.newDomain())
+
+ val duration = 5 * 60L
+ val workloadA =
+ SimTraceWorkload(
+ SimTrace.ofFragments(
+ SimTraceFragment(0, duration * 1000, 0.0, 1),
+ SimTraceFragment(duration * 1000, duration * 1000, 28.0, 1),
+ SimTraceFragment(duration * 2000, duration * 1000, 3500.0, 1),
+ SimTraceFragment(duration * 3000, duration * 1000, 183.0, 1)
+ ),
+ )
+ val workloadB =
+ SimTraceWorkload(
+ SimTrace.ofFragments(
+ SimTraceFragment(0, duration * 1000, 0.0, 1),
+ SimTraceFragment(duration * 1000, duration * 1000, 28.0, 1),
+ SimTraceFragment(duration * 2000, duration * 1000, 3100.0, 1),
+ SimTraceFragment(duration * 3000, duration * 1000, 73.0, 1)
+ )
+ )
+
+ launch {
+ machine.run(hypervisor)
+ }
+
+ coroutineScope {
+ launch {
+ val vm = hypervisor.createMachine(model, "a")
+ vm.run(workloadA)
+ vm.close()
+ }
+ val vm = hypervisor.createMachine(model, "b")
+ vm.run(workloadB)
+ vm.close()
+ }
+
+ 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/kernel/SimSpaceSharedHypervisorTest.kt
index ef6f536d..02d308ff 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/kernel/SimSpaceSharedHypervisorTest.kt
@@ -20,38 +20,37 @@
* SOFTWARE.
*/
-package org.opendc.simulator.compute
+package org.opendc.simulator.compute.kernel
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.SimBareMetalMachine
+import org.opendc.simulator.compute.model.MachineModel
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.compute.power.SimplePowerDriver
+import org.opendc.simulator.compute.workload.*
import org.opendc.simulator.core.runBlockingSimulation
+import org.opendc.simulator.flow.FlowEngine
/**
* A test suite for the [SimSpaceSharedHypervisor].
*/
@OptIn(ExperimentalCoroutinesApi::class)
internal class SimSpaceSharedHypervisorTest {
- private lateinit var machineModel: SimMachineModel
+ private lateinit var machineModel: MachineModel
@BeforeEach
fun setUp() {
val cpuNode = ProcessingNode("Intel", "Xeon", "amd64", 1)
- machineModel = SimMachineModel(
+ machineModel = MachineModel(
cpus = List(cpuNode.coreCount) { ProcessingUnit(cpuNode, it, 3200.0) },
memory = List(4) { MemoryUnit("Crucial", "MTA18ASF4G72AZ-3G2B1", 3200.0, 32_000) }
)
@@ -62,47 +61,30 @@ internal class SimSpaceSharedHypervisorTest {
*/
@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)
+ SimTrace.ofFragments(
+ SimTraceFragment(0, duration * 1000, 28.0, 1),
+ SimTraceFragment(duration * 1000, duration * 1000, 3500.0, 1),
+ SimTraceFragment(duration * 2000, duration * 1000, 0.0, 1),
+ SimTraceFragment(duration * 3000, duration * 1000, 183.0, 1)
),
)
- val machine = SimBareMetalMachine(
- coroutineContext, clock, machineModel, PerformanceScalingGovernor(),
- SimpleScalingDriver(ConstantPowerModel(0.0))
- )
- val hypervisor = SimSpaceSharedHypervisor()
+ val engine = FlowEngine(coroutineContext, clock)
+ val machine = SimBareMetalMachine(engine, machineModel, SimplePowerDriver(ConstantPowerModel(0.0)))
+ val hypervisor = SimSpaceSharedHypervisor(engine, null, null)
- 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" } }
- )
+ assertEquals(5 * 60L * 4000, clock.millis()) { "Took enough time" }
}
/**
@@ -112,11 +94,9 @@ internal class SimSpaceSharedHypervisorTest {
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()
+ val engine = FlowEngine(coroutineContext, clock)
+ val machine = SimBareMetalMachine(engine, machineModel, SimplePowerDriver(ConstantPowerModel(0.0)))
+ val hypervisor = SimSpaceSharedHypervisor(engine, null, null)
launch { machine.run(hypervisor) }
yield()
@@ -135,11 +115,11 @@ internal class SimSpaceSharedHypervisorTest {
fun testFlopsWorkload() = runBlockingSimulation {
val duration = 5 * 60L * 1000
val workload = SimFlopsWorkload((duration * 3.2).toLong(), 1.0)
+ val engine = FlowEngine(coroutineContext, clock)
val machine = SimBareMetalMachine(
- coroutineContext, clock, machineModel, PerformanceScalingGovernor(),
- SimpleScalingDriver(ConstantPowerModel(0.0))
+ engine, machineModel, SimplePowerDriver(ConstantPowerModel(0.0))
)
- val hypervisor = SimSpaceSharedHypervisor()
+ val hypervisor = SimSpaceSharedHypervisor(engine, null, null)
launch { machine.run(hypervisor) }
yield()
@@ -156,11 +136,11 @@ internal class SimSpaceSharedHypervisorTest {
@Test
fun testTwoWorkloads() = runBlockingSimulation {
val duration = 5 * 60L * 1000
+ val engine = FlowEngine(coroutineContext, clock)
val machine = SimBareMetalMachine(
- coroutineContext, clock, machineModel, PerformanceScalingGovernor(),
- SimpleScalingDriver(ConstantPowerModel(0.0))
+ engine, machineModel, SimplePowerDriver(ConstantPowerModel(0.0))
)
- val hypervisor = SimSpaceSharedHypervisor()
+ val hypervisor = SimSpaceSharedHypervisor(engine, null, null)
launch { machine.run(hypervisor) }
yield()
@@ -169,6 +149,8 @@ internal class SimSpaceSharedHypervisorTest {
vm.run(SimRuntimeWorkload(duration))
vm.close()
+ yield()
+
val vm2 = hypervisor.createMachine(machineModel)
vm2.run(SimRuntimeWorkload(duration))
vm2.close()
@@ -182,11 +164,9 @@ internal class SimSpaceSharedHypervisorTest {
*/
@Test
fun testConcurrentWorkloadFails() = runBlockingSimulation {
- val machine = SimBareMetalMachine(
- coroutineContext, clock, machineModel, PerformanceScalingGovernor(),
- SimpleScalingDriver(ConstantPowerModel(0.0))
- )
- val hypervisor = SimSpaceSharedHypervisor()
+ val engine = FlowEngine(coroutineContext, clock)
+ val machine = SimBareMetalMachine(engine, machineModel, SimplePowerDriver(ConstantPowerModel(0.0)))
+ val hypervisor = SimSpaceSharedHypervisor(engine, null, null)
launch { machine.run(hypervisor) }
yield()
@@ -206,11 +186,11 @@ internal class SimSpaceSharedHypervisorTest {
*/
@Test
fun testConcurrentWorkloadSucceeds() = runBlockingSimulation {
+ val interpreter = FlowEngine(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, null, null)
launch { machine.run(hypervisor) }
yield()
diff --git a/opendc-simulator/opendc-simulator-compute/src/test/kotlin/org/opendc/simulator/compute/kernel/cpufreq/ConservativeScalingGovernorTest.kt b/opendc-simulator/opendc-simulator-compute/src/test/kotlin/org/opendc/simulator/compute/kernel/cpufreq/ConservativeScalingGovernorTest.kt
new file mode 100644
index 00000000..ef354569
--- /dev/null
+++ b/opendc-simulator/opendc-simulator-compute/src/test/kotlin/org/opendc/simulator/compute/kernel/cpufreq/ConservativeScalingGovernorTest.kt
@@ -0,0 +1,98 @@
+/*
+ * 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.kernel.cpufreq
+
+import io.mockk.every
+import io.mockk.mockk
+import io.mockk.verify
+import org.junit.jupiter.api.Assertions.assertEquals
+import org.junit.jupiter.api.Test
+
+/**
+ * Test suite for the [ConservativeScalingGovernor]
+ */
+internal class ConservativeScalingGovernorTest {
+ @Test
+ fun testSetStartLimitWithoutPStates() {
+ val cpuCapacity = 4100.0
+ val minSpeed = cpuCapacity / 2
+ val defaultThreshold = 0.8
+ val defaultStepSize = 0.05 * cpuCapacity
+ val governor = ConservativeScalingGovernor()
+
+ val policy = mockk<ScalingPolicy>(relaxUnitFun = true)
+ every { policy.max } returns cpuCapacity
+ every { policy.min } returns minSpeed
+
+ var target = 0.0
+ every { policy.target } answers { target }
+ every { policy.target = any() } propertyType Double::class answers { target = value }
+
+ val logic = governor.createLogic(policy)
+ logic.onStart()
+ assertEquals(defaultThreshold, governor.threshold)
+
+ logic.onLimit(0.5)
+
+ /* Upwards scaling */
+ logic.onLimit(defaultThreshold + 0.2)
+
+ /* Downwards scaling */
+ logic.onLimit(defaultThreshold + 0.1)
+
+ verify(exactly = 2) { policy.target = minSpeed }
+ verify(exactly = 1) { policy.target = minSpeed + defaultStepSize }
+ }
+
+ @Test
+ fun testSetStartLimitWithPStatesAndParams() {
+ val firstPState = 1000.0
+ val cpuCapacity = 4100.0
+ val minSpeed = firstPState
+ val threshold = 0.5
+ val stepSize = 0.02 * cpuCapacity
+ val governor = ConservativeScalingGovernor(threshold, stepSize)
+
+ val policy = mockk<ScalingPolicy>(relaxUnitFun = true)
+ every { policy.max } returns cpuCapacity
+ every { policy.min } returns firstPState
+
+ var target = 0.0
+ every { policy.target } answers { target }
+ every { policy.target = any() } propertyType Double::class answers { target = value }
+
+ val logic = governor.createLogic(policy)
+ logic.onStart()
+ assertEquals(threshold, governor.threshold)
+ logic.onLimit(0.5)
+
+ /* Upwards scaling */
+ logic.onLimit(threshold + 0.2)
+
+ /* Downwards scaling */
+ logic.onLimit(threshold + 0.1)
+
+ verify(exactly = 2) { policy.target = minSpeed }
+ verify(exactly = 1) { policy.target = minSpeed + stepSize }
+ }
+}
diff --git a/opendc-simulator/opendc-simulator-compute/src/test/kotlin/org/opendc/simulator/compute/kernel/cpufreq/OnDemandScalingGovernorTest.kt b/opendc-simulator/opendc-simulator-compute/src/test/kotlin/org/opendc/simulator/compute/kernel/cpufreq/OnDemandScalingGovernorTest.kt
new file mode 100644
index 00000000..ca759e39
--- /dev/null
+++ b/opendc-simulator/opendc-simulator-compute/src/test/kotlin/org/opendc/simulator/compute/kernel/cpufreq/OnDemandScalingGovernorTest.kt
@@ -0,0 +1,81 @@
+/*
+ * 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.kernel.cpufreq
+
+import io.mockk.every
+import io.mockk.mockk
+import io.mockk.verify
+import org.junit.jupiter.api.Assertions.assertEquals
+import org.junit.jupiter.api.Test
+
+/**
+ * Test suite for the [OnDemandScalingGovernor]
+ */
+internal class OnDemandScalingGovernorTest {
+ @Test
+ fun testSetStartLimitWithoutPStates() {
+ val cpuCapacity = 4100.0
+ val minSpeed = cpuCapacity / 2
+ val defaultThreshold = 0.8
+ val governor = OnDemandScalingGovernor()
+
+ val policy = mockk<ScalingPolicy>(relaxUnitFun = true)
+ every { policy.min } returns minSpeed
+ every { policy.max } returns cpuCapacity
+
+ val logic = governor.createLogic(policy)
+ logic.onStart()
+ assertEquals(defaultThreshold, governor.threshold)
+ verify(exactly = 1) { policy.target = minSpeed }
+
+ logic.onLimit(0.5)
+ verify(exactly = 1) { policy.target = minSpeed + 0.5 * (cpuCapacity - minSpeed) / 100 }
+
+ logic.onLimit(defaultThreshold)
+ verify(exactly = 1) { policy.target = cpuCapacity }
+ }
+
+ @Test
+ fun testSetStartLimitWithPStatesAndParams() {
+ val firstPState = 1000.0
+ val cpuCapacity = 4100.0
+ val threshold = 0.5
+ val governor = OnDemandScalingGovernor(threshold)
+
+ val policy = mockk<ScalingPolicy>(relaxUnitFun = true)
+ every { policy.max } returns cpuCapacity
+ every { policy.min } returns firstPState
+
+ val logic = governor.createLogic(policy)
+
+ logic.onStart()
+ assertEquals(threshold, governor.threshold)
+ verify(exactly = 1) { policy.target = firstPState }
+
+ logic.onLimit(0.1)
+ verify(exactly = 1) { policy.target = firstPState + 0.1 * (cpuCapacity - firstPState) / 100 }
+
+ logic.onLimit(threshold)
+ verify(exactly = 1) { policy.target = cpuCapacity }
+ }
+}
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/kernel/cpufreq/PerformanceScalingGovernorTest.kt
index c482d348..a4bb24f2 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/kernel/cpufreq/PerformanceScalingGovernorTest.kt
@@ -20,29 +20,31 @@
* SOFTWARE.
*/
-package org.opendc.simulator.compute.cpufreq
+package org.opendc.simulator.compute.kernel.cpufreq
import io.mockk.every
-import io.mockk.mockk
+import io.mockk.spyk
import io.mockk.verify
import org.junit.jupiter.api.Test
/**
- * 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 policy = spyk<ScalingPolicy>()
+ val logic = PerformanceScalingGovernor().createLogic(policy)
- every { ctx.cpu.speed } returns 2100.0
-
- val logic = DemandScalingGovernor().createLogic(ctx)
+ every { policy.max } returns 4100.0
logic.onStart()
- verify(exactly = 0) { ctx.setTarget(any()) }
+ verify(exactly = 1) { policy.target = 4100.0 }
+
+ logic.onLimit(0.0)
+ verify(exactly = 1) { policy.target = 4100.0 }
- logic.onLimit()
- verify(exactly = 1) { ctx.setTarget(2100.0) }
+ logic.onLimit(1.0)
+ verify(exactly = 1) { policy.target = 4100.0 }
}
}
diff --git a/opendc-simulator/opendc-simulator-compute/src/test/kotlin/org/opendc/simulator/compute/kernel/cpufreq/PowerSaveScalingGovernorTest.kt b/opendc-simulator/opendc-simulator-compute/src/test/kotlin/org/opendc/simulator/compute/kernel/cpufreq/PowerSaveScalingGovernorTest.kt
new file mode 100644
index 00000000..662d55fb
--- /dev/null
+++ b/opendc-simulator/opendc-simulator-compute/src/test/kotlin/org/opendc/simulator/compute/kernel/cpufreq/PowerSaveScalingGovernorTest.kt
@@ -0,0 +1,72 @@
+/*
+ * 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.kernel.cpufreq
+
+import io.mockk.every
+import io.mockk.mockk
+import io.mockk.verify
+import org.junit.jupiter.api.Test
+
+/**
+ * Test suite for the [PowerSaveScalingGovernor]
+ */
+internal class PowerSaveScalingGovernorTest {
+ @Test
+ fun testSetStartLimitWithoutPStates() {
+ val cpuCapacity = 4100.0
+ val minSpeed = cpuCapacity / 2
+ val policy = mockk<ScalingPolicy>(relaxUnitFun = true)
+ val logic = PowerSaveScalingGovernor().createLogic(policy)
+
+ every { policy.max } returns cpuCapacity
+ every { policy.min } returns minSpeed
+
+ logic.onStart()
+
+ logic.onLimit(0.0)
+ verify(exactly = 1) { policy.target = minSpeed }
+
+ logic.onLimit(1.0)
+ verify(exactly = 1) { policy.target = minSpeed }
+ }
+
+ @Test
+ fun testSetStartLimitWithPStates() {
+ val cpuCapacity = 4100.0
+ val firstPState = 1000.0
+ val policy = mockk<ScalingPolicy>(relaxUnitFun = true)
+ val logic = PowerSaveScalingGovernor().createLogic(policy)
+
+ every { policy.max } returns cpuCapacity
+ every { policy.min } returns firstPState
+
+ logic.onStart()
+ verify(exactly = 1) { policy.target = firstPState }
+
+ logic.onLimit(0.0)
+ verify(exactly = 1) { policy.target = firstPState }
+
+ logic.onLimit(1.0)
+ verify(exactly = 1) { policy.target = firstPState }
+ }
+}
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..f557c8d3 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>()
+ val cpu = mockk<SimProcessingUnit>(relaxUnitFun = true)
- every { cpu.model.frequency } returns 4100.0
- every { cpu.speed } returns 1200.0
+ every { cpu.capacity } returns 3200.0
+ every { cpu.rate } 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,24 @@ 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 cpu = mockk<SimProcessingUnit>(relaxUnitFun = true)
+ val cpus = listOf(cpu, cpu)
+
+ every { cpus[0].capacity } returns 1000.0
+ every { cpus[0].rate } returns 1200.0
- every { cpu.model.frequency } returns 4100.0
- every { cpu.speed } returns 1200.0
+ every { cpus[1].capacity } returns 3500.0
+ every { cpus[1].rate } returns 1200.0
- val driver = PStateScalingDriver(
+ val driver = PStatePowerDriver(
sortedMapOf(
2800.0 to ConstantPowerModel(200.0),
3300.0 to ConstantPowerModel(300.0),
@@ -91,13 +90,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())
}
@@ -105,11 +98,11 @@ internal class PStateScalingDriverTest {
@Test
fun testPowerBasedOnUtilization() {
val machine = mockk<SimBareMetalMachine>()
- val cpu = mockk<SimProcessingUnit>()
+ val cpu = mockk<SimProcessingUnit>(relaxUnitFun = true)
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 +110,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.rate } returns 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.rate } returns 1400.0
+ every { cpu.capacity } returns 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
index dd93302b..7852534a 100644
--- 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
@@ -1,3 +1,25 @@
+/*
+ * 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.power
import org.junit.jupiter.api.Assertions.assertAll
@@ -39,7 +61,8 @@ internal class PowerModelTest {
@Test
fun `compute power draw by the SPEC benchmark model`() {
- val powerModel = InterpolationPowerModel("IBMx3550M3_XeonX5675")
+ val ibm = listOf(58.4, 98.0, 109.0, 118.0, 128.0, 140.0, 153.0, 170.0, 189.0, 205.0, 222.0)
+ val powerModel = InterpolationPowerModel(ibm)
assertAll(
{ assertEquals(58.4, powerModel.computePower(0.0)) },
diff --git a/opendc-simulator/opendc-simulator-compute/src/test/kotlin/org/opendc/simulator/compute/workload/SimTraceWorkloadTest.kt b/opendc-simulator/opendc-simulator-compute/src/test/kotlin/org/opendc/simulator/compute/workload/SimTraceWorkloadTest.kt
new file mode 100644
index 00000000..574860e8
--- /dev/null
+++ b/opendc-simulator/opendc-simulator-compute/src/test/kotlin/org/opendc/simulator/compute/workload/SimTraceWorkloadTest.kt
@@ -0,0 +1,160 @@
+/*
+ * 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.workload
+
+import kotlinx.coroutines.delay
+import org.junit.jupiter.api.Assertions.assertEquals
+import org.junit.jupiter.api.BeforeEach
+import org.junit.jupiter.api.Test
+import org.opendc.simulator.compute.SimBareMetalMachine
+import org.opendc.simulator.compute.model.*
+import org.opendc.simulator.compute.power.ConstantPowerModel
+import org.opendc.simulator.compute.power.SimplePowerDriver
+import org.opendc.simulator.core.runBlockingSimulation
+import org.opendc.simulator.flow.FlowEngine
+
+/**
+ * Test suite for the [SimTraceWorkloadTest] class.
+ */
+class SimTraceWorkloadTest {
+ private lateinit var machineModel: MachineModel
+
+ @BeforeEach
+ fun setUp() {
+ val cpuNode = ProcessingNode("Intel", "Xeon", "amd64", 2)
+
+ machineModel = MachineModel(
+ cpus = List(cpuNode.coreCount) { ProcessingUnit(cpuNode, it, 1000.0) },
+ memory = List(4) { MemoryUnit("Crucial", "MTA18ASF4G72AZ-3G2B1", 3200.0, 32_000) }
+ )
+ }
+
+ @Test
+ fun testSmoke() = runBlockingSimulation {
+ val machine = SimBareMetalMachine(
+ FlowEngine(coroutineContext, clock),
+ machineModel,
+ SimplePowerDriver(ConstantPowerModel(0.0))
+ )
+
+ val workload = SimTraceWorkload(
+ SimTrace.ofFragments(
+ SimTraceFragment(0, 1000, 2 * 28.0, 2),
+ SimTraceFragment(1000, 1000, 2 * 3100.0, 2),
+ SimTraceFragment(2000, 1000, 0.0, 2),
+ SimTraceFragment(3000, 1000, 2 * 73.0, 2)
+ ),
+ offset = 0
+ )
+
+ try {
+ machine.run(workload)
+
+ assertEquals(4000, clock.millis())
+ } finally {
+ machine.close()
+ }
+ }
+
+ @Test
+ fun testOffset() = runBlockingSimulation {
+ val machine = SimBareMetalMachine(
+ FlowEngine(coroutineContext, clock),
+ machineModel,
+ SimplePowerDriver(ConstantPowerModel(0.0))
+ )
+
+ val workload = SimTraceWorkload(
+ SimTrace.ofFragments(
+ SimTraceFragment(0, 1000, 2 * 28.0, 2),
+ SimTraceFragment(1000, 1000, 2 * 3100.0, 2),
+ SimTraceFragment(2000, 1000, 0.0, 2),
+ SimTraceFragment(3000, 1000, 2 * 73.0, 2)
+ ),
+ offset = 1000
+ )
+
+ try {
+ machine.run(workload)
+
+ assertEquals(5000, clock.millis())
+ } finally {
+ machine.close()
+ }
+ }
+
+ @Test
+ fun testSkipFragment() = runBlockingSimulation {
+ val machine = SimBareMetalMachine(
+ FlowEngine(coroutineContext, clock),
+ machineModel,
+ SimplePowerDriver(ConstantPowerModel(0.0))
+ )
+
+ val workload = SimTraceWorkload(
+ SimTrace.ofFragments(
+ SimTraceFragment(0, 1000, 2 * 28.0, 2),
+ SimTraceFragment(1000, 1000, 2 * 3100.0, 2),
+ SimTraceFragment(2000, 1000, 0.0, 2),
+ SimTraceFragment(3000, 1000, 2 * 73.0, 2)
+ ),
+ offset = 0
+ )
+
+ try {
+ delay(1000L)
+ machine.run(workload)
+
+ assertEquals(4000, clock.millis())
+ } finally {
+ machine.close()
+ }
+ }
+
+ @Test
+ fun testZeroCores() = runBlockingSimulation {
+ val machine = SimBareMetalMachine(
+ FlowEngine(coroutineContext, clock),
+ machineModel,
+ SimplePowerDriver(ConstantPowerModel(0.0))
+ )
+
+ val workload = SimTraceWorkload(
+ SimTrace.ofFragments(
+ SimTraceFragment(0, 1000, 2 * 28.0, 2),
+ SimTraceFragment(1000, 1000, 2 * 3100.0, 2),
+ SimTraceFragment(2000, 1000, 0.0, 0),
+ SimTraceFragment(3000, 1000, 2 * 73.0, 2)
+ ),
+ offset = 0
+ )
+
+ try {
+ machine.run(workload)
+
+ assertEquals(4000, clock.millis())
+ } finally {
+ machine.close()
+ }
+ }
+}
diff --git a/opendc-simulator/opendc-simulator-core/src/main/kotlin/org/opendc/simulator/core/SimulationCoroutineDispatcher.kt b/opendc-simulator/opendc-simulator-core/src/main/kotlin/org/opendc/simulator/core/SimulationCoroutineDispatcher.kt
index e2f7874c..908e902a 100644
--- a/opendc-simulator/opendc-simulator-core/src/main/kotlin/org/opendc/simulator/core/SimulationCoroutineDispatcher.kt
+++ b/opendc-simulator/opendc-simulator-core/src/main/kotlin/org/opendc/simulator/core/SimulationCoroutineDispatcher.kt
@@ -37,11 +37,6 @@ import kotlin.coroutines.CoroutineContext
@OptIn(InternalCoroutinesApi::class)
public class SimulationCoroutineDispatcher : CoroutineDispatcher(), SimulationController, Delay {
/**
- * The virtual clock of this dispatcher.
- */
- override val clock: Clock = VirtualClock()
-
- /**
* Queue of ordered tasks to run.
*/
private val queue = PriorityQueue<TimedRunnable>()
@@ -54,7 +49,12 @@ public class SimulationCoroutineDispatcher : CoroutineDispatcher(), SimulationCo
/**
* The current virtual time of simulation
*/
- private var _time = 0L
+ private var _clock = SimClock()
+
+ /**
+ * The virtual clock of this dispatcher.
+ */
+ override val clock: Clock = ClockAdapter(_clock)
override fun dispatch(context: CoroutineContext, block: Runnable) {
block.run()
@@ -79,14 +79,14 @@ public class SimulationCoroutineDispatcher : CoroutineDispatcher(), SimulationCo
}
override fun toString(): String {
- return "SimulationCoroutineDispatcher[time=${_time}ms, queued=${queue.size}]"
+ return "SimulationCoroutineDispatcher[time=${_clock.time}ms, queued=${queue.size}]"
}
private fun post(block: Runnable) =
queue.add(TimedRunnable(block, _counter++))
private fun postDelayed(block: Runnable, delayTime: Long) =
- TimedRunnable(block, _counter++, safePlus(_time, delayTime))
+ TimedRunnable(block, _counter++, safePlus(_clock.time, delayTime))
.also {
queue.add(it)
}
@@ -100,31 +100,41 @@ public class SimulationCoroutineDispatcher : CoroutineDispatcher(), SimulationCo
override fun advanceUntilIdle(): Long {
val queue = queue
- val oldTime = _time
- while (queue.isNotEmpty()) {
- val current = queue.poll()
+ val clock = _clock
+ val oldTime = clock.time
+
+ while (true) {
+ val current = queue.poll() ?: break
// If the scheduled time is 0 (immediate) use current virtual time
if (current.time != 0L) {
- _time = current.time
+ clock.time = current.time
}
current.run()
}
- return _time - oldTime
+ return clock.time - oldTime
}
- private inner class VirtualClock(private val zone: ZoneId = ZoneId.systemDefault()) : Clock() {
+ /**
+ * A helper class that holds the time of the simulation.
+ */
+ private class SimClock(@JvmField var time: Long = 0)
+
+ /**
+ * A helper class to expose a [Clock] instance for this dispatcher.
+ */
+ private class ClockAdapter(private val clock: SimClock, private val zone: ZoneId = ZoneId.systemDefault()) : Clock() {
override fun getZone(): ZoneId = zone
- override fun withZone(zone: ZoneId): Clock = VirtualClock(zone)
+ override fun withZone(zone: ZoneId): Clock = ClockAdapter(clock, zone)
override fun instant(): Instant = Instant.ofEpochMilli(millis())
- override fun millis(): Long = _time
+ override fun millis(): Long = clock.time
- override fun toString(): String = "SimulationCoroutineDispatcher.VirtualClock[time=$_time]"
+ override fun toString(): String = "SimulationCoroutineDispatcher.ClockAdapter[time=${clock.time}]"
}
/**
diff --git a/opendc-simulator/opendc-simulator-failures/src/main/kotlin/org/opendc/simulator/failures/CorrelatedFaultInjector.kt b/opendc-simulator/opendc-simulator-failures/src/main/kotlin/org/opendc/simulator/failures/CorrelatedFaultInjector.kt
deleted file mode 100644
index 0e15f338..00000000
--- a/opendc-simulator/opendc-simulator-failures/src/main/kotlin/org/opendc/simulator/failures/CorrelatedFaultInjector.kt
+++ /dev/null
@@ -1,129 +0,0 @@
-/*
- * 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.failures
-
-import kotlinx.coroutines.*
-import java.time.Clock
-import kotlin.math.exp
-import kotlin.math.max
-import kotlin.random.Random
-import kotlin.random.asJavaRandom
-
-/**
- * A [FaultInjector] that injects fault in the system which are correlated to each other. Failures do not occur in
- * isolation, but will trigger other faults.
- */
-public class CorrelatedFaultInjector(
- private val coroutineScope: CoroutineScope,
- private val clock: Clock,
- private val iatScale: Double,
- private val iatShape: Double,
- private val sizeScale: Double,
- private val sizeShape: Double,
- private val dScale: Double,
- private val dShape: Double,
- random: Random = Random(0)
-) : FaultInjector {
- /**
- * The active failure domains that have been registered.
- */
- private val active = mutableSetOf<FailureDomain>()
-
- /**
- * The [Job] that awaits the nearest fault in the system.
- */
- private var job: Job? = null
-
- /**
- * The [Random] instance to use.
- */
- private val random: java.util.Random = random.asJavaRandom()
-
- /**
- * Enqueue the specified [FailureDomain] to fail some time in the future.
- */
- override fun enqueue(domain: FailureDomain) {
- active += domain
-
- // Clean up the domain if it finishes
- domain.scope.coroutineContext[Job]!!.invokeOnCompletion {
- this@CorrelatedFaultInjector.coroutineScope.launch {
- active -= domain
-
- if (active.isEmpty()) {
- job?.cancel()
- job = null
- }
- }
- }
-
- if (job != null) {
- return
- }
-
- job = this.coroutineScope.launch {
- while (active.isNotEmpty()) {
- ensureActive()
-
- // Make sure to convert delay from hours to milliseconds
- val d = lognvariate(iatScale, iatShape) * 3.6e6
-
- // Handle long overflow
- if (clock.millis() + d <= 0) {
- return@launch
- }
-
- delay(d.toLong())
-
- val n = lognvariate(sizeScale, sizeShape).toInt()
- val targets = active.shuffled(random).take(n)
-
- for (failureDomain in targets) {
- active -= failureDomain
- failureDomain.fail()
- }
-
- val df = max(lognvariate(dScale, dShape) * 6e4, 15 * 6e4)
-
- // Handle long overflow
- if (clock.millis() + df <= 0) {
- return@launch
- }
-
- delay(df.toLong())
-
- for (failureDomain in targets) {
- failureDomain.recover()
-
- // Re-enqueue machine to be failed
- enqueue(failureDomain)
- }
- }
-
- job = null
- }
- }
-
- // XXX We should extract this in some common package later on.
- private fun lognvariate(scale: Double, shape: Double) = exp(scale + shape * random.nextGaussian())
-}
diff --git a/opendc-simulator/opendc-simulator-failures/src/main/kotlin/org/opendc/simulator/failures/FailureDomain.kt b/opendc-simulator/opendc-simulator-failures/src/main/kotlin/org/opendc/simulator/failures/FailureDomain.kt
deleted file mode 100644
index dc3006e8..00000000
--- a/opendc-simulator/opendc-simulator-failures/src/main/kotlin/org/opendc/simulator/failures/FailureDomain.kt
+++ /dev/null
@@ -1,47 +0,0 @@
-/*
- * MIT License
- *
- * 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.failures
-
-import kotlinx.coroutines.CoroutineScope
-
-/**
- * A logical or physical component in a computing environment which may fail.
- */
-public interface FailureDomain {
- /**
- * The lifecycle of the failure domain to which a [FaultInjector] will attach.
- */
- public val scope: CoroutineScope
-
- /**
- * Fail the domain externally.
- */
- public suspend fun fail()
-
- /**
- * Resume the failure domain.
- */
- public suspend fun recover()
-}
diff --git a/opendc-simulator/opendc-simulator-failures/src/main/kotlin/org/opendc/simulator/failures/UncorrelatedFaultInjector.kt b/opendc-simulator/opendc-simulator-failures/src/main/kotlin/org/opendc/simulator/failures/UncorrelatedFaultInjector.kt
deleted file mode 100644
index b3bd737e..00000000
--- a/opendc-simulator/opendc-simulator-failures/src/main/kotlin/org/opendc/simulator/failures/UncorrelatedFaultInjector.kt
+++ /dev/null
@@ -1,61 +0,0 @@
-/*
- * 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.failures
-
-import kotlinx.coroutines.delay
-import kotlinx.coroutines.launch
-import java.time.Clock
-import kotlin.math.ln1p
-import kotlin.math.pow
-import kotlin.random.Random
-
-/**
- * A [FaultInjector] that injects uncorrelated faults into the system, meaning that failures of the subsystems are
- * independent.
- */
-public class UncorrelatedFaultInjector(
- private val clock: Clock,
- private val alpha: Double,
- private val beta: Double,
- private val random: Random = Random(0)
-) : FaultInjector {
- /**
- * Enqueue the specified [FailureDomain] to fail some time in the future.
- */
- override fun enqueue(domain: FailureDomain) {
- domain.scope.launch {
- val d = random.weibull(alpha, beta) * 1e3 // Make sure to convert delay to milliseconds
-
- // Handle long overflow
- if (clock.millis() + d <= 0) {
- return@launch
- }
-
- delay(d.toLong())
- domain.fail()
- }
- }
-
- // XXX We should extract this in some common package later on.
- private fun Random.weibull(alpha: Double, beta: Double) = (beta * (-ln1p(-nextDouble())).pow(1.0 / alpha))
-}
diff --git a/opendc-simulator/opendc-simulator-resources/build.gradle.kts b/opendc-simulator/opendc-simulator-flow/build.gradle.kts
index e4ffc3ff..05e21c3c 100644
--- a/opendc-simulator/opendc-simulator-resources/build.gradle.kts
+++ b/opendc-simulator/opendc-simulator-flow/build.gradle.kts
@@ -20,7 +20,7 @@
* SOFTWARE.
*/
-description = "Uniform resource consumption simulation model"
+description = "High-performance flow simulator"
plugins {
`kotlin-library-conventions`
@@ -32,8 +32,8 @@ plugins {
dependencies {
api(platform(projects.opendcPlatform))
api(libs.kotlinx.coroutines)
- implementation(projects.opendcUtils)
+ implementation(libs.kotlin.logging)
- jmhImplementation(projects.opendcSimulator.opendcSimulatorCore)
testImplementation(projects.opendcSimulator.opendcSimulatorCore)
+ testImplementation(libs.slf4j.simple)
}
diff --git a/opendc-simulator/opendc-simulator-flow/src/jmh/kotlin/org/opendc/simulator/flow/FlowBenchmarks.kt b/opendc-simulator/opendc-simulator-flow/src/jmh/kotlin/org/opendc/simulator/flow/FlowBenchmarks.kt
new file mode 100644
index 00000000..aabd2220
--- /dev/null
+++ b/opendc-simulator/opendc-simulator-flow/src/jmh/kotlin/org/opendc/simulator/flow/FlowBenchmarks.kt
@@ -0,0 +1,133 @@
+/*
+ * 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.flow
+
+import kotlinx.coroutines.ExperimentalCoroutinesApi
+import kotlinx.coroutines.launch
+import org.opendc.simulator.core.runBlockingSimulation
+import org.opendc.simulator.flow.mux.ForwardingFlowMultiplexer
+import org.opendc.simulator.flow.mux.MaxMinFlowMultiplexer
+import org.opendc.simulator.flow.source.TraceFlowSource
+import org.openjdk.jmh.annotations.*
+import java.util.concurrent.ThreadLocalRandom
+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 FlowBenchmarks {
+ private lateinit var trace: Sequence<TraceFlowSource.Fragment>
+
+ @Setup
+ fun setUp() {
+ val random = ThreadLocalRandom.current()
+ val entries = List(10000) { TraceFlowSource.Fragment(1000, random.nextDouble(0.0, 4500.0)) }
+ trace = entries.asSequence()
+ }
+
+ @Benchmark
+ fun benchmarkSink() {
+ return runBlockingSimulation {
+ val engine = FlowEngine(coroutineContext, clock)
+ val provider = FlowSink(engine, 4200.0)
+ return@runBlockingSimulation provider.consume(TraceFlowSource(trace))
+ }
+ }
+
+ @Benchmark
+ fun benchmarkForward() {
+ return runBlockingSimulation {
+ val engine = FlowEngine(coroutineContext, clock)
+ val provider = FlowSink(engine, 4200.0)
+ val forwarder = FlowForwarder(engine)
+ provider.startConsumer(forwarder)
+ return@runBlockingSimulation forwarder.consume(TraceFlowSource(trace))
+ }
+ }
+
+ @Benchmark
+ fun benchmarkMuxMaxMinSingleSource() {
+ return runBlockingSimulation {
+ val engine = FlowEngine(coroutineContext, clock)
+ val switch = MaxMinFlowMultiplexer(engine)
+
+ FlowSink(engine, 3000.0).startConsumer(switch.newOutput())
+ FlowSink(engine, 3000.0).startConsumer(switch.newOutput())
+
+ val provider = switch.newInput()
+ return@runBlockingSimulation provider.consume(TraceFlowSource(trace))
+ }
+ }
+
+ @Benchmark
+ fun benchmarkMuxMaxMinTripleSource() {
+ return runBlockingSimulation {
+ val engine = FlowEngine(coroutineContext, clock)
+ val switch = MaxMinFlowMultiplexer(engine)
+
+ FlowSink(engine, 3000.0).startConsumer(switch.newOutput())
+ FlowSink(engine, 3000.0).startConsumer(switch.newOutput())
+
+ repeat(3) {
+ launch {
+ val provider = switch.newInput()
+ provider.consume(TraceFlowSource(trace))
+ }
+ }
+ }
+ }
+
+ @Benchmark
+ fun benchmarkMuxExclusiveSingleSource() {
+ return runBlockingSimulation {
+ val engine = FlowEngine(coroutineContext, clock)
+ val switch = ForwardingFlowMultiplexer(engine)
+
+ FlowSink(engine, 3000.0).startConsumer(switch.newOutput())
+ FlowSink(engine, 3000.0).startConsumer(switch.newOutput())
+
+ val provider = switch.newInput()
+ return@runBlockingSimulation provider.consume(TraceFlowSource(trace))
+ }
+ }
+
+ @Benchmark
+ fun benchmarkMuxExclusiveTripleSource() {
+ return runBlockingSimulation {
+ val engine = FlowEngine(coroutineContext, clock)
+ val switch = ForwardingFlowMultiplexer(engine)
+
+ FlowSink(engine, 3000.0).startConsumer(switch.newOutput())
+ FlowSink(engine, 3000.0).startConsumer(switch.newOutput())
+
+ repeat(2) {
+ launch {
+ val provider = switch.newInput()
+ provider.consume(TraceFlowSource(trace))
+ }
+ }
+ }
+ }
+}
diff --git a/opendc-simulator/opendc-simulator-flow/src/main/kotlin/org/opendc/simulator/flow/FlowConnection.kt b/opendc-simulator/opendc-simulator-flow/src/main/kotlin/org/opendc/simulator/flow/FlowConnection.kt
new file mode 100644
index 00000000..8ff0bc76
--- /dev/null
+++ b/opendc-simulator/opendc-simulator-flow/src/main/kotlin/org/opendc/simulator/flow/FlowConnection.kt
@@ -0,0 +1,72 @@
+/*
+ * 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.flow
+
+/**
+ * An active connection between a [FlowSource] and [FlowConsumer].
+ */
+public interface FlowConnection : AutoCloseable {
+ /**
+ * The capacity of the connection.
+ */
+ public val capacity: Double
+
+ /**
+ * The flow rate over the connection.
+ */
+ public val rate: Double
+
+ /**
+ * The flow demand of the source.
+ */
+ public val demand: Double
+
+ /**
+ * A flag to control whether [FlowSource.onConverge] should be invoked for this source.
+ */
+ public var shouldSourceConverge: Boolean
+
+ /**
+ * Pull the source.
+ */
+ public fun pull()
+
+ /**
+ * Pull the source.
+ *
+ * @param now The timestamp at which the connection is pulled.
+ */
+ public fun pull(now: Long)
+
+ /**
+ * Push the given flow [rate] over this connection.
+ *
+ * @param rate The rate of the flow to push.
+ */
+ public fun push(rate: Double)
+
+ /**
+ * Disconnect the consumer from its source.
+ */
+ public override fun close()
+}
diff --git a/opendc-simulator/opendc-simulator-flow/src/main/kotlin/org/opendc/simulator/flow/FlowConsumer.kt b/opendc-simulator/opendc-simulator-flow/src/main/kotlin/org/opendc/simulator/flow/FlowConsumer.kt
new file mode 100644
index 00000000..4685a755
--- /dev/null
+++ b/opendc-simulator/opendc-simulator-flow/src/main/kotlin/org/opendc/simulator/flow/FlowConsumer.kt
@@ -0,0 +1,131 @@
+/*
+ * 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.flow
+
+import kotlinx.coroutines.suspendCancellableCoroutine
+import kotlin.coroutines.resume
+import kotlin.coroutines.resumeWithException
+
+/**
+ * A consumer of a [FlowSource].
+ */
+public interface FlowConsumer {
+ /**
+ * A flag to indicate that the consumer is currently consuming a [FlowSource].
+ */
+ public val isActive: Boolean
+
+ /**
+ * The flow capacity of this consumer.
+ */
+ public val capacity: Double
+
+ /**
+ * The current flow rate of the consumer.
+ */
+ public val rate: Double
+
+ /**
+ * The current flow demand.
+ */
+ public val demand: Double
+
+ /**
+ * The flow counters to track the flow metrics of the consumer.
+ */
+ public val counters: FlowCounters
+
+ /**
+ * Start consuming the specified [source].
+ *
+ * @throws IllegalStateException if the consumer is already active.
+ */
+ public fun startConsumer(source: FlowSource)
+
+ /**
+ * Ask the consumer to pull its source.
+ *
+ * If the consumer is not active, this operation will be a no-op.
+ */
+ public fun pull()
+
+ /**
+ * Disconnect the consumer from its source.
+ *
+ * If the consumer is not active, this operation will be a no-op.
+ */
+ public fun cancel()
+}
+
+/**
+ * Consume the specified [source] and suspend execution until the source is fully consumed or failed.
+ */
+public suspend fun FlowConsumer.consume(source: FlowSource) {
+ return suspendCancellableCoroutine { cont ->
+ startConsumer(object : FlowSource {
+ override fun onStart(conn: FlowConnection, now: Long) {
+ try {
+ source.onStart(conn, now)
+ } catch (cause: Throwable) {
+ cont.resumeWithException(cause)
+ throw cause
+ }
+ }
+
+ override fun onStop(conn: FlowConnection, now: Long, delta: Long) {
+ try {
+ source.onStop(conn, now, delta)
+
+ if (!cont.isCompleted) {
+ cont.resume(Unit)
+ }
+ } catch (cause: Throwable) {
+ cont.resumeWithException(cause)
+ throw cause
+ }
+ }
+
+ override fun onPull(conn: FlowConnection, now: Long, delta: Long): Long {
+ return try {
+ source.onPull(conn, now, delta)
+ } catch (cause: Throwable) {
+ cont.resumeWithException(cause)
+ throw cause
+ }
+ }
+
+ override fun onConverge(conn: FlowConnection, now: Long, delta: Long) {
+ try {
+ source.onConverge(conn, now, delta)
+ } catch (cause: Throwable) {
+ cont.resumeWithException(cause)
+ throw cause
+ }
+ }
+
+ override fun toString(): String = "SuspendingFlowSource"
+ })
+
+ cont.invokeOnCancellation { cancel() }
+ }
+}
diff --git a/opendc-simulator/opendc-simulator-flow/src/main/kotlin/org/opendc/simulator/flow/FlowConsumerContext.kt b/opendc-simulator/opendc-simulator-flow/src/main/kotlin/org/opendc/simulator/flow/FlowConsumerContext.kt
new file mode 100644
index 00000000..98922ab3
--- /dev/null
+++ b/opendc-simulator/opendc-simulator-flow/src/main/kotlin/org/opendc/simulator/flow/FlowConsumerContext.kt
@@ -0,0 +1,62 @@
+/*
+ * 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.flow
+
+/**
+ * A controllable [FlowConnection].
+ *
+ * This interface is used by [FlowConsumer]s to control the connection between it and the source.
+ */
+public interface FlowConsumerContext : FlowConnection {
+ /**
+ * The deadline of the source.
+ */
+ public val deadline: Long
+
+ /**
+ * The capacity of the connection.
+ */
+ public override var capacity: Double
+
+ /**
+ * A flag to control whether [FlowConsumerLogic.onConverge] should be invoked for the consumer.
+ */
+ public var shouldConsumerConverge: Boolean
+
+ /**
+ * A flag to control whether the timers for the [FlowSource] should be enabled.
+ */
+ public var enableTimers: Boolean
+
+ /**
+ * Start the flow over the connection.
+ */
+ public fun start()
+
+ /**
+ * Synchronously pull the source of the connection.
+ *
+ * @param now The timestamp at which the connection is pulled.
+ */
+ public fun pullSync(now: Long)
+}
diff --git a/opendc-simulator/opendc-simulator-flow/src/main/kotlin/org/opendc/simulator/flow/FlowConsumerLogic.kt b/opendc-simulator/opendc-simulator-flow/src/main/kotlin/org/opendc/simulator/flow/FlowConsumerLogic.kt
new file mode 100644
index 00000000..50fbc9c7
--- /dev/null
+++ b/opendc-simulator/opendc-simulator-flow/src/main/kotlin/org/opendc/simulator/flow/FlowConsumerLogic.kt
@@ -0,0 +1,60 @@
+/*
+ * 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.flow
+
+/**
+ * A collection of callbacks associated with a [FlowConsumer].
+ */
+public interface FlowConsumerLogic {
+ /**
+ * This method is invoked when a [FlowSource] changes the rate of flow to this consumer.
+ *
+ * @param ctx The context in which the provider runs.
+ * @param now The virtual timestamp in milliseconds at which the update is occurring.
+ * @param delta The virtual duration between this call and the last call to [onPush] in milliseconds.
+ * @param rate The requested processing rate of the source.
+ */
+ public fun onPush(ctx: FlowConsumerContext, now: Long, delta: Long, rate: Double) {}
+
+ /**
+ * This method is invoked when the flow graph has converged into a steady-state system.
+ *
+ * Make sure to enable [FlowConsumerContext.shouldSourceConverge] if you need this callback. By default, this method
+ * will not be invoked.
+ *
+ * @param ctx The context in which the provider runs.
+ * @param now The virtual timestamp in milliseconds at which the system converged.
+ * @param delta The virtual duration between this call and the last call to [onConverge] in milliseconds.
+ */
+ public fun onConverge(ctx: FlowConsumerContext, now: Long, delta: Long) {}
+
+ /**
+ * This method is invoked when the [FlowSource] completed or failed.
+ *
+ * @param ctx The context in which the provider runs.
+ * @param now The virtual timestamp in milliseconds at which the provider finished.
+ * @param delta The virtual duration between this call and the last call to [onPush] in milliseconds.
+ * @param cause The cause of the failure or `null` if the source completed.
+ */
+ public fun onFinish(ctx: FlowConsumerContext, now: Long, delta: Long, cause: Throwable?) {}
+}
diff --git a/opendc-simulator/opendc-simulator-flow/src/main/kotlin/org/opendc/simulator/flow/FlowConvergenceListener.kt b/opendc-simulator/opendc-simulator-flow/src/main/kotlin/org/opendc/simulator/flow/FlowConvergenceListener.kt
new file mode 100644
index 00000000..d1afda6f
--- /dev/null
+++ b/opendc-simulator/opendc-simulator-flow/src/main/kotlin/org/opendc/simulator/flow/FlowConvergenceListener.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.flow
+
+/**
+ * A listener interface for when a flow stage has converged into a steady-state.
+ */
+public interface FlowConvergenceListener {
+ /**
+ * This method is invoked when the system has converged to a steady-state.
+ *
+ * @param now The timestamp at which the system converged.
+ * @param delta The virtual duration between this call and the last call to [onConverge] in milliseconds.
+ */
+ public fun onConverge(now: Long, delta: Long) {}
+}
diff --git a/opendc-simulator/opendc-simulator-resources/src/main/kotlin/org/opendc/simulator/resources/SimResourceAggregator.kt b/opendc-simulator/opendc-simulator-flow/src/main/kotlin/org/opendc/simulator/flow/FlowCounters.kt
index bb4e6a2c..a717ae6e 100644
--- a/opendc-simulator/opendc-simulator-resources/src/main/kotlin/org/opendc/simulator/resources/SimResourceAggregator.kt
+++ b/opendc-simulator/opendc-simulator-flow/src/main/kotlin/org/opendc/simulator/flow/FlowCounters.kt
@@ -20,29 +20,34 @@
* SOFTWARE.
*/
-package org.opendc.simulator.resources
+package org.opendc.simulator.flow
/**
- * A [SimResourceAggregator] aggregates the capacity of multiple resources into a single resource.
+ * An interface that tracks cumulative counts of the flow accumulation over a stage.
*/
-public interface SimResourceAggregator : AutoCloseable {
+public interface FlowCounters {
/**
- * The output resource provider to which resource consumers can be attached.
+ * The accumulated flow that a source wanted to push over the connection.
*/
- public val output: SimResourceProvider
+ public val demand: Double
/**
- * The input resources that will be switched between the output providers.
+ * The accumulated flow that was actually transferred over the connection.
*/
- public val inputs: Set<SimResourceProvider>
+ public val actual: Double
/**
- * Add the specified [input] to the switch.
+ * The amount of capacity that was not utilized.
*/
- public fun addInput(input: SimResourceProvider)
+ public val remaining: Double
/**
- * End the lifecycle of the aggregator.
+ * The accumulated flow lost due to interference between sources.
*/
- public override fun close()
+ public val interference: Double
+
+ /**
+ * Reset the flow counters.
+ */
+ public fun reset()
}
diff --git a/opendc-simulator/opendc-simulator-flow/src/main/kotlin/org/opendc/simulator/flow/FlowEngine.kt b/opendc-simulator/opendc-simulator-flow/src/main/kotlin/org/opendc/simulator/flow/FlowEngine.kt
new file mode 100644
index 00000000..65224827
--- /dev/null
+++ b/opendc-simulator/opendc-simulator-flow/src/main/kotlin/org/opendc/simulator/flow/FlowEngine.kt
@@ -0,0 +1,95 @@
+/*
+ * 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.flow
+
+import org.opendc.simulator.flow.internal.FlowEngineImpl
+import java.time.Clock
+import kotlin.coroutines.CoroutineContext
+
+/**
+ * A [FlowEngine] is responsible for managing the interaction between [FlowSource]s and [FlowConsumer]s.
+ *
+ * The engine centralizes the scheduling logic of state updates of flow connections, allowing update propagation
+ * to happen more efficiently. and overall, reducing the work necessary to transition into a steady state.
+ */
+public interface FlowEngine {
+ /**
+ * The virtual [Clock] associated with this engine.
+ */
+ public val clock: Clock
+
+ /**
+ * Create a new [FlowConsumerContext] with the given [provider].
+ *
+ * @param consumer The consumer logic.
+ * @param provider The logic of the resource provider.
+ */
+ public fun newContext(consumer: FlowSource, provider: FlowConsumerLogic): FlowConsumerContext
+
+ /**
+ * Start batching the execution of resource updates until [popBatch] is called.
+ *
+ * This method is useful if you want to propagate multiple resources updates (e.g., starting multiple CPUs
+ * simultaneously) in a single state update.
+ *
+ * Multiple calls to this method requires the same number of [popBatch] calls in order to properly flush the
+ * resource updates. This allows nested calls to [pushBatch], but might cause issues if [popBatch] is not called
+ * the same amount of times. To simplify batching, see [batch].
+ */
+ public fun pushBatch()
+
+ /**
+ * Stop the batching of resource updates and run the interpreter on the batch.
+ *
+ * Note that method will only flush the event once the first call to [pushBatch] has received a [popBatch] call.
+ */
+ public fun popBatch()
+
+ public companion object {
+ /**
+ * Construct a new [FlowEngine] implementation.
+ *
+ * @param context The coroutine context to use.
+ * @param clock The virtual simulation clock.
+ */
+ @JvmStatic
+ @JvmName("create")
+ public operator fun invoke(context: CoroutineContext, clock: Clock): FlowEngine {
+ return FlowEngineImpl(context, clock)
+ }
+ }
+}
+
+/**
+ * Batch the execution of several interrupts into a single call.
+ *
+ * This method is useful if you want to propagate the start of multiple resources (e.g., CPUs) in a single update.
+ */
+public inline fun FlowEngine.batch(block: () -> Unit) {
+ try {
+ pushBatch()
+ block()
+ } finally {
+ popBatch()
+ }
+}
diff --git a/opendc-simulator/opendc-simulator-flow/src/main/kotlin/org/opendc/simulator/flow/FlowForwarder.kt b/opendc-simulator/opendc-simulator-flow/src/main/kotlin/org/opendc/simulator/flow/FlowForwarder.kt
new file mode 100644
index 00000000..e3bdd7ba
--- /dev/null
+++ b/opendc-simulator/opendc-simulator-flow/src/main/kotlin/org/opendc/simulator/flow/FlowForwarder.kt
@@ -0,0 +1,256 @@
+/*
+ * 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.flow
+
+import mu.KotlinLogging
+import org.opendc.simulator.flow.internal.D_MS_TO_S
+import org.opendc.simulator.flow.internal.MutableFlowCounters
+import kotlin.math.max
+
+/**
+ * A class that acts as a [FlowSource] and [FlowConsumer] at the same time.
+ *
+ * @param engine The [FlowEngine] the forwarder runs in.
+ * @param isCoupled A flag to indicate that the transformer will exit when the resource consumer exits.
+ */
+public class FlowForwarder(private val engine: FlowEngine, private val isCoupled: Boolean = false) : FlowSource, FlowConsumer, AutoCloseable {
+ /**
+ * The logging instance of this connection.
+ */
+ private val logger = KotlinLogging.logger {}
+
+ /**
+ * The delegate [FlowSource].
+ */
+ private var delegate: FlowSource? = null
+
+ /**
+ * A flag to indicate that the delegate was started.
+ */
+ private var hasDelegateStarted: Boolean = false
+
+ /**
+ * The exposed [FlowConnection].
+ */
+ private val _ctx = object : FlowConnection {
+ override var shouldSourceConverge: Boolean = false
+ set(value) {
+ field = value
+ _innerCtx?.shouldSourceConverge = value
+ }
+
+ override val capacity: Double
+ get() = _innerCtx?.capacity ?: 0.0
+
+ override val demand: Double
+ get() = _innerCtx?.demand ?: 0.0
+
+ override val rate: Double
+ get() = _innerCtx?.rate ?: 0.0
+
+ override fun pull() {
+ _innerCtx?.pull()
+ }
+
+ override fun pull(now: Long) {
+ _innerCtx?.pull(now)
+ }
+
+ @JvmField var lastPull = Long.MAX_VALUE
+
+ override fun push(rate: Double) {
+ if (delegate == null) {
+ return
+ }
+
+ _innerCtx?.push(rate)
+ _demand = rate
+ }
+
+ override fun close() {
+ val delegate = delegate ?: return
+ val hasDelegateStarted = hasDelegateStarted
+
+ // Warning: resumption of the continuation might change the entire state of the forwarder. Make sure we
+ // reset beforehand the existing state and check whether it has been updated afterwards
+ reset()
+
+ if (hasDelegateStarted) {
+ val now = engine.clock.millis()
+ val delta = max(0, now - lastPull)
+ delegate.onStop(this, now, delta)
+ }
+ }
+ }
+
+ /**
+ * The [FlowConnection] in which the forwarder runs.
+ */
+ private var _innerCtx: FlowConnection? = null
+
+ override val isActive: Boolean
+ get() = delegate != null
+
+ override val capacity: Double
+ get() = _ctx.capacity
+
+ override val rate: Double
+ get() = _ctx.rate
+
+ override val demand: Double
+ get() = _ctx.demand
+
+ override val counters: FlowCounters
+ get() = _counters
+ private val _counters = MutableFlowCounters()
+
+ override fun startConsumer(source: FlowSource) {
+ check(delegate == null) { "Forwarder already active" }
+
+ delegate = source
+
+ // Pull to replace the source
+ pull()
+ }
+
+ override fun pull() {
+ _ctx.pull()
+ }
+
+ override fun cancel() {
+ _ctx.close()
+ }
+
+ override fun close() {
+ val ctx = _innerCtx
+
+ if (ctx != null) {
+ this._innerCtx = null
+ ctx.pull()
+ }
+ }
+
+ override fun onStart(conn: FlowConnection, now: Long) {
+ _innerCtx = conn
+
+ if (_ctx.shouldSourceConverge) {
+ conn.shouldSourceConverge = true
+ }
+ }
+
+ override fun onStop(conn: FlowConnection, now: Long, delta: Long) {
+ _innerCtx = null
+
+ val delegate = delegate
+ if (delegate != null) {
+ reset()
+
+ try {
+ delegate.onStop(this._ctx, now, delta)
+ } catch (cause: Throwable) {
+ logger.error(cause) { "Uncaught exception" }
+ }
+ }
+ }
+
+ override fun onPull(conn: FlowConnection, now: Long, delta: Long): Long {
+ val delegate = delegate
+
+ if (!hasDelegateStarted) {
+ start()
+ }
+
+ _ctx.lastPull = now
+ updateCounters(conn, delta)
+
+ return try {
+ delegate?.onPull(_ctx, now, delta) ?: Long.MAX_VALUE
+ } catch (cause: Throwable) {
+ logger.error(cause) { "Uncaught exception" }
+
+ reset()
+ Long.MAX_VALUE
+ }
+ }
+
+ override fun onConverge(conn: FlowConnection, now: Long, delta: Long) {
+ try {
+ delegate?.onConverge(this._ctx, now, delta)
+ } catch (cause: Throwable) {
+ logger.error(cause) { "Uncaught exception" }
+
+ _innerCtx = null
+ reset()
+ }
+ }
+
+ /**
+ * Start the delegate.
+ */
+ private fun start() {
+ val delegate = delegate ?: return
+
+ try {
+ delegate.onStart(_ctx, engine.clock.millis())
+ hasDelegateStarted = true
+ } catch (cause: Throwable) {
+ logger.error(cause) { "Uncaught exception" }
+ reset()
+ }
+ }
+
+ /**
+ * Reset the delegate.
+ */
+ private fun reset() {
+ if (isCoupled)
+ _innerCtx?.close()
+ else
+ _innerCtx?.push(0.0)
+
+ delegate = null
+ hasDelegateStarted = false
+ }
+
+ /**
+ * The requested flow rate.
+ */
+ private var _demand: Double = 0.0
+
+ /**
+ * Update the flow counters for the transformer.
+ */
+ private fun updateCounters(ctx: FlowConnection, delta: Long) {
+ if (delta <= 0) {
+ return
+ }
+
+ val counters = _counters
+ val deltaS = delta * D_MS_TO_S
+ val total = ctx.capacity * deltaS
+ val work = _demand * deltaS
+ val actualWork = ctx.rate * deltaS
+
+ counters.increment(work, actualWork, (total - actualWork), 0.0)
+ }
+}
diff --git a/opendc-simulator/opendc-simulator-flow/src/main/kotlin/org/opendc/simulator/flow/FlowMapper.kt b/opendc-simulator/opendc-simulator-flow/src/main/kotlin/org/opendc/simulator/flow/FlowMapper.kt
new file mode 100644
index 00000000..6867bcef
--- /dev/null
+++ b/opendc-simulator/opendc-simulator-flow/src/main/kotlin/org/opendc/simulator/flow/FlowMapper.kt
@@ -0,0 +1,75 @@
+/*
+ * 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.flow
+
+/**
+ * A [FlowConsumer] that maps the pushed flow through [transform].
+ *
+ * @param source The source of the flow.
+ * @param transform The method to transform the flow.
+ */
+public class FlowMapper(
+ private val source: FlowSource,
+ private val transform: (FlowConnection, Double) -> Double
+) : FlowSource {
+
+ /**
+ * The current active connection.
+ */
+ private var _conn: Connection? = null
+
+ override fun onStart(conn: FlowConnection, now: Long) {
+ check(_conn == null) { "Concurrent access" }
+ val delegate = Connection(conn, transform)
+ _conn = delegate
+ source.onStart(delegate, now)
+ }
+
+ override fun onStop(conn: FlowConnection, now: Long, delta: Long) {
+ val delegate = checkNotNull(_conn) { "Invariant violation" }
+ _conn = null
+ source.onStop(delegate, now, delta)
+ }
+
+ override fun onPull(conn: FlowConnection, now: Long, delta: Long): Long {
+ val delegate = checkNotNull(_conn) { "Invariant violation" }
+ return source.onPull(delegate, now, delta)
+ }
+
+ override fun onConverge(conn: FlowConnection, now: Long, delta: Long) {
+ val delegate = _conn ?: return
+ source.onConverge(delegate, now, delta)
+ }
+
+ /**
+ * The wrapper [FlowConnection] that is used to transform the flow.
+ */
+ private class Connection(
+ private val delegate: FlowConnection,
+ private val transform: (FlowConnection, Double) -> Double
+ ) : FlowConnection by delegate {
+ override fun push(rate: Double) {
+ delegate.push(transform(this, rate))
+ }
+ }
+}
diff --git a/opendc-simulator/opendc-simulator-flow/src/main/kotlin/org/opendc/simulator/flow/FlowSink.kt b/opendc-simulator/opendc-simulator-flow/src/main/kotlin/org/opendc/simulator/flow/FlowSink.kt
new file mode 100644
index 00000000..e9094443
--- /dev/null
+++ b/opendc-simulator/opendc-simulator-flow/src/main/kotlin/org/opendc/simulator/flow/FlowSink.kt
@@ -0,0 +1,155 @@
+/*
+ * 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.flow
+
+import org.opendc.simulator.flow.internal.D_MS_TO_S
+import org.opendc.simulator.flow.internal.MutableFlowCounters
+
+/**
+ * A [FlowSink] represents a sink with a fixed capacity.
+ *
+ * @param initialCapacity The initial capacity of the resource.
+ * @param engine The engine that is used for driving the flow simulation.
+ * @param parent The parent flow system.
+ */
+public class FlowSink(
+ private val engine: FlowEngine,
+ initialCapacity: Double,
+ private val parent: FlowConvergenceListener? = null
+) : FlowConsumer {
+ /**
+ * A flag to indicate that the flow consumer is active.
+ */
+ public override val isActive: Boolean
+ get() = _ctx != null
+
+ /**
+ * The capacity of the consumer.
+ */
+ public override var capacity: Double = initialCapacity
+ set(value) {
+ field = value
+ _ctx?.capacity = value
+ }
+
+ /**
+ * The current processing rate of the consumer.
+ */
+ public override val rate: Double
+ get() = _ctx?.rate ?: 0.0
+
+ /**
+ * The flow processing rate demand at this instant.
+ */
+ public override val demand: Double
+ get() = _ctx?.demand ?: 0.0
+
+ /**
+ * The flow counters to track the flow metrics of the consumer.
+ */
+ public override val counters: FlowCounters
+ get() = _counters
+ private val _counters = MutableFlowCounters()
+
+ /**
+ * The current active [FlowConsumerLogic] of this sink.
+ */
+ private var _ctx: FlowConsumerContext? = null
+
+ override fun startConsumer(source: FlowSource) {
+ check(_ctx == null) { "Consumer is in invalid state" }
+
+ val ctx = engine.newContext(source, Logic(parent, _counters))
+ _ctx = ctx
+
+ ctx.capacity = capacity
+ if (parent != null) {
+ ctx.shouldConsumerConverge = true
+ }
+
+ ctx.start()
+ }
+
+ override fun pull() {
+ _ctx?.pull()
+ }
+
+ override fun cancel() {
+ _ctx?.close()
+ }
+
+ override fun toString(): String = "FlowSink[capacity=$capacity]"
+
+ /**
+ * [FlowConsumerLogic] of a sink.
+ */
+ private inner class Logic(private val parent: FlowConvergenceListener?, private val counters: MutableFlowCounters) : FlowConsumerLogic {
+ override fun onPush(
+ ctx: FlowConsumerContext,
+ now: Long,
+ delta: Long,
+ rate: Double
+ ) {
+ updateCounters(ctx, delta, rate, ctx.capacity)
+ }
+
+ override fun onFinish(ctx: FlowConsumerContext, now: Long, delta: Long, cause: Throwable?) {
+ updateCounters(ctx, delta, 0.0, 0.0)
+
+ _ctx = null
+ }
+
+ override fun onConverge(ctx: FlowConsumerContext, now: Long, delta: Long) {
+ parent?.onConverge(now, delta)
+ }
+
+ /**
+ * The previous demand and capacity for the consumer.
+ */
+ private val _previous = DoubleArray(2)
+
+ /**
+ * Update the counters of the flow consumer.
+ */
+ private fun updateCounters(ctx: FlowConnection, delta: Long, nextDemand: Double, nextCapacity: Double) {
+ val counters = counters
+ val previous = _previous
+ val demand = previous[0]
+ val capacity = previous[1]
+
+ previous[0] = nextDemand
+ previous[1] = nextCapacity
+
+ if (delta <= 0) {
+ return
+ }
+
+ val deltaS = delta * D_MS_TO_S
+ val total = demand * deltaS
+ val work = capacity * deltaS
+ val actualWork = ctx.rate * deltaS
+
+ counters.increment(work, actualWork, (total - actualWork), 0.0)
+ }
+ }
+}
diff --git a/opendc-simulator/opendc-simulator-flow/src/main/kotlin/org/opendc/simulator/flow/FlowSource.kt b/opendc-simulator/opendc-simulator-flow/src/main/kotlin/org/opendc/simulator/flow/FlowSource.kt
new file mode 100644
index 00000000..3a7e52aa
--- /dev/null
+++ b/opendc-simulator/opendc-simulator-flow/src/main/kotlin/org/opendc/simulator/flow/FlowSource.kt
@@ -0,0 +1,70 @@
+/*
+ * 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.flow
+
+/**
+ * A source of flow that is consumed by a [FlowConsumer].
+ *
+ * Implementations of this interface should be considered stateful and must be assumed not to be re-usable
+ * (concurrently) for multiple [FlowConsumer]s, unless explicitly said otherwise.
+ */
+public interface FlowSource {
+ /**
+ * This method is invoked when the source is started.
+ *
+ * @param conn The connection between the source and consumer.
+ * @param now The virtual timestamp in milliseconds at which the provider finished.
+ */
+ public fun onStart(conn: FlowConnection, now: Long) {}
+
+ /**
+ * This method is invoked when the source is finished.
+ *
+ * @param conn The connection between the source and consumer.
+ * @param now The virtual timestamp in milliseconds at which the source finished.
+ * @param delta The virtual duration between this call and the last call to [onPull] in milliseconds.
+ */
+ public fun onStop(conn: FlowConnection, now: Long, delta: Long) {}
+
+ /**
+ * This method is invoked when the source is pulled.
+ *
+ * @param conn The connection between the source and consumer.
+ * @param now The virtual timestamp in milliseconds at which the pull is occurring.
+ * @param delta The virtual duration between this call and the last call to [onPull] in milliseconds.
+ * @return The duration after which the resource consumer should be pulled again.
+ */
+ public fun onPull(conn: FlowConnection, now: Long, delta: Long): Long
+
+ /**
+ * This method is invoked when the flow graph has converged into a steady-state system.
+ *
+ * Make sure to enable [FlowConnection.shouldSourceConverge] if you need this callback. By default, this method
+ * will not be invoked.
+ *
+ * @param conn The connection between the source and consumer.
+ * @param now The virtual timestamp in milliseconds at which the system converged.
+ * @param delta The virtual duration between this call and the last call to [onConverge] in milliseconds.
+ */
+ public fun onConverge(conn: FlowConnection, now: Long, delta: Long) {}
+}
diff --git a/opendc-simulator/opendc-simulator-flow/src/main/kotlin/org/opendc/simulator/flow/interference/InterferenceDomain.kt b/opendc-simulator/opendc-simulator-flow/src/main/kotlin/org/opendc/simulator/flow/interference/InterferenceDomain.kt
new file mode 100644
index 00000000..aa2713b6
--- /dev/null
+++ b/opendc-simulator/opendc-simulator-flow/src/main/kotlin/org/opendc/simulator/flow/interference/InterferenceDomain.kt
@@ -0,0 +1,19 @@
+package org.opendc.simulator.flow.interference
+
+import org.opendc.simulator.flow.FlowSource
+
+/**
+ * An interference domain represents a system of flow stages where [flow sources][FlowSource] may incur
+ * performance variability due to operating on the same resource and therefore causing interference.
+ */
+public interface InterferenceDomain {
+ /**
+ * Compute the performance score of a participant in this interference domain.
+ *
+ * @param key The participant to obtain the score of or `null` if the participant has no key.
+ * @param load The overall load on the interference domain.
+ * @return A score representing the performance score to be applied to the resource consumer, with 1
+ * meaning no influence, <1 means that performance degrades, and >1 means that performance improves.
+ */
+ public fun apply(key: InterferenceKey?, load: Double): Double
+}
diff --git a/opendc-simulator/opendc-simulator-failures/build.gradle.kts b/opendc-simulator/opendc-simulator-flow/src/main/kotlin/org/opendc/simulator/flow/interference/InterferenceKey.kt
index 57cd0a35..d28ebde5 100644
--- a/opendc-simulator/opendc-simulator-failures/build.gradle.kts
+++ b/opendc-simulator/opendc-simulator-flow/src/main/kotlin/org/opendc/simulator/flow/interference/InterferenceKey.kt
@@ -1,5 +1,5 @@
/*
- * Copyright (c) 2020 AtLarge Research
+ * 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
@@ -20,13 +20,9 @@
* SOFTWARE.
*/
-description = "Failure models for OpenDC"
+package org.opendc.simulator.flow.interference
-plugins {
- `kotlin-library-conventions`
-}
-
-dependencies {
- api(platform(projects.opendcPlatform))
- api(libs.kotlinx.coroutines)
-}
+/**
+ * A key that uniquely identifies a participant of an interference domain.
+ */
+public interface InterferenceKey
diff --git a/opendc-simulator/opendc-simulator-resources/src/main/kotlin/org/opendc/simulator/resources/SimResourceFlow.kt b/opendc-simulator/opendc-simulator-flow/src/main/kotlin/org/opendc/simulator/flow/internal/Constants.kt
index bbf6ad44..450195ec 100644
--- a/opendc-simulator/opendc-simulator-resources/src/main/kotlin/org/opendc/simulator/resources/SimResourceFlow.kt
+++ b/opendc-simulator/opendc-simulator-flow/src/main/kotlin/org/opendc/simulator/flow/internal/Constants.kt
@@ -20,10 +20,9 @@
* SOFTWARE.
*/
-package org.opendc.simulator.resources
+package org.opendc.simulator.flow.internal
/**
- * A [SimResourceFlow] acts as both a resource consumer and resource provider at the same time, simplifying bridging
- * between different components.
+ * Constant for converting milliseconds into seconds.
*/
-public interface SimResourceFlow : SimResourceConsumer, SimResourceProvider
+internal const val D_MS_TO_S = 1 / 1000.0
diff --git a/opendc-simulator/opendc-simulator-flow/src/main/kotlin/org/opendc/simulator/flow/internal/Flags.kt b/opendc-simulator/opendc-simulator-flow/src/main/kotlin/org/opendc/simulator/flow/internal/Flags.kt
new file mode 100644
index 00000000..97d56fff
--- /dev/null
+++ b/opendc-simulator/opendc-simulator-flow/src/main/kotlin/org/opendc/simulator/flow/internal/Flags.kt
@@ -0,0 +1,44 @@
+/*
+ * 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.flow.internal
+
+/**
+ * States of the flow connection.
+ */
+internal const val ConnPending = 0 // Connection is pending and the consumer is waiting to consume the source
+internal const val ConnActive = 1 // Connection is active and the source is currently being consumed
+internal const val ConnClosed = 2 // Connection is closed and source cannot be consumed through this connection anymore
+internal const val ConnState = 0b11 // Mask for accessing the state of the flow connection
+
+/**
+ * Flags of the flow connection
+ */
+internal const val ConnPulled = 1 shl 2 // The source should be pulled
+internal const val ConnPushed = 1 shl 3 // The source has pushed a value
+internal const val ConnClose = 1 shl 4 // The connection should be closed
+internal const val ConnUpdateActive = 1 shl 5 // An update for the connection is active
+internal const val ConnUpdatePending = 1 shl 6 // An (immediate) update of the connection is pending
+internal const val ConnConvergePending = 1 shl 7 // Indication that a convergence is already pending
+internal const val ConnConvergeSource = 1 shl 8 // Enable convergence of the source
+internal const val ConnConvergeConsumer = 1 shl 9 // Enable convergence of the consumer
+internal const val ConnDisableTimers = 1 shl 10 // Disable timers for the source
diff --git a/opendc-simulator/opendc-simulator-flow/src/main/kotlin/org/opendc/simulator/flow/internal/FlowConsumerContextImpl.kt b/opendc-simulator/opendc-simulator-flow/src/main/kotlin/org/opendc/simulator/flow/internal/FlowConsumerContextImpl.kt
new file mode 100644
index 00000000..58ca918b
--- /dev/null
+++ b/opendc-simulator/opendc-simulator-flow/src/main/kotlin/org/opendc/simulator/flow/internal/FlowConsumerContextImpl.kt
@@ -0,0 +1,452 @@
+/*
+ * 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.flow.internal
+
+import mu.KotlinLogging
+import org.opendc.simulator.flow.*
+import java.util.*
+import kotlin.math.max
+import kotlin.math.min
+
+/**
+ * Implementation of a [FlowConnection] managing the communication between flow sources and consumers.
+ */
+internal class FlowConsumerContextImpl(
+ private val engine: FlowEngineImpl,
+ private val source: FlowSource,
+ private val logic: FlowConsumerLogic
+) : FlowConsumerContext {
+ /**
+ * The logging instance of this connection.
+ */
+ private val logger = KotlinLogging.logger {}
+
+ /**
+ * The capacity of the connection.
+ */
+ override var capacity: Double
+ get() = _capacity
+ set(value) {
+ val oldValue = _capacity
+
+ // Only changes will be propagated
+ if (value != oldValue) {
+ _capacity = value
+ pull()
+ }
+ }
+ private var _capacity: Double = 0.0
+
+ /**
+ * The current processing rate of the connection.
+ */
+ override val rate: Double
+ get() = _rate
+ private var _rate = 0.0
+
+ /**
+ * The current flow processing demand.
+ */
+ override val demand: Double
+ get() = _demand
+ private var _demand: Double = 0.0 // The current (pending) demand of the source
+
+ /**
+ * The deadline of the source.
+ */
+ override val deadline: Long
+ get() = _deadline
+ private var _deadline: Long = Long.MAX_VALUE // The deadline of the source's timer
+
+ /**
+ * Flags to control the convergence of the consumer and source.
+ */
+ override var shouldSourceConverge: Boolean
+ get() = _flags and ConnConvergeSource == ConnConvergeSource
+ set(value) {
+ _flags =
+ if (value)
+ _flags or ConnConvergeSource
+ else
+ _flags and ConnConvergeSource.inv()
+ }
+ override var shouldConsumerConverge: Boolean
+ get() = _flags and ConnConvergeConsumer == ConnConvergeConsumer
+ set(value) {
+ _flags =
+ if (value)
+ _flags or ConnConvergeConsumer
+ else
+ _flags and ConnConvergeConsumer.inv()
+ }
+
+ /**
+ * Flag to control the timers on the [FlowSource]
+ */
+ override var enableTimers: Boolean
+ get() = _flags and ConnDisableTimers != ConnDisableTimers
+ set(value) {
+ _flags =
+ if (!value)
+ _flags or ConnDisableTimers
+ else
+ _flags and ConnDisableTimers.inv()
+ }
+
+ /**
+ * The clock to track simulation time.
+ */
+ private val _clock = engine.clock
+
+ /**
+ * The flags of the flow connection, indicating certain actions.
+ */
+ private var _flags: Int = 0
+
+ /**
+ * The timestamp of calls to the callbacks.
+ */
+ private var _lastPull: Long = Long.MIN_VALUE // Last call to `onPull`
+ private var _lastPush: Long = Long.MIN_VALUE // Last call to `onPush`
+ private var _lastSourceConvergence: Long = Long.MAX_VALUE // Last call to source `onConvergence`
+ private var _lastConsumerConvergence: Long = Long.MAX_VALUE // Last call to consumer `onConvergence`
+
+ /**
+ * The timers at which the context is scheduled to be interrupted.
+ */
+ private var _timer: Long = Long.MAX_VALUE
+ private val _pendingTimers: ArrayDeque<Long> = ArrayDeque(5)
+
+ override fun start() {
+ check(_flags and ConnState == ConnPending) { "Consumer is already started" }
+ engine.batch {
+ val now = _clock.millis()
+ source.onStart(this, now)
+
+ // Mark the connection as active and pulled
+ val newFlags = (_flags and ConnState.inv()) or ConnActive or ConnPulled
+ scheduleImmediate(now, newFlags)
+ }
+ }
+
+ override fun close() {
+ val flags = _flags
+ if (flags and ConnState == ConnClosed) {
+ return
+ }
+
+ // Toggle the close bit. In case no update is active, schedule a new update.
+ if (flags and ConnUpdateActive == 0) {
+ val now = _clock.millis()
+ scheduleImmediate(now, flags or ConnClose)
+ } else {
+ _flags = flags or ConnClose
+ }
+ }
+
+ override fun pull(now: Long) {
+ val flags = _flags
+ if (flags and ConnState != ConnActive) {
+ return
+ }
+
+ // Mark connection as pulled
+ scheduleImmediate(now, flags or ConnPulled)
+ }
+
+ override fun pull() {
+ pull(_clock.millis())
+ }
+
+ override fun pullSync(now: Long) {
+ val flags = _flags
+
+ // Do not attempt to flush the connection if the connection is closed or an update is already active
+ if (flags and ConnState != ConnActive || flags and ConnUpdateActive != 0) {
+ return
+ }
+
+ if (flags and (ConnPulled or ConnPushed) != 0 || _deadline == now) {
+ engine.scheduleSync(now, this)
+ }
+ }
+
+ override fun push(rate: Double) {
+ if (_demand == rate) {
+ return
+ }
+
+ _demand = rate
+
+ val flags = _flags
+
+ if (flags and ConnUpdateActive != 0) {
+ // If an update is active, it will already get picked up at the end of the update
+ _flags = flags or ConnPushed
+ } else {
+ // Invalidate only if no update is active
+ scheduleImmediate(_clock.millis(), flags or ConnPushed)
+ }
+ }
+
+ /**
+ * Update the state of the flow connection.
+ *
+ * @param now The current virtual timestamp.
+ * @param visited The queue of connections that have been visited during the cycle.
+ * @param timerQueue The queue of all pending timers.
+ * @param isImmediate A flag to indicate that this invocation is an immediate update or a delayed update.
+ */
+ fun doUpdate(
+ now: Long,
+ visited: FlowDeque,
+ timerQueue: FlowTimerQueue,
+ isImmediate: Boolean
+ ) {
+ var flags = _flags
+
+ // Precondition: The flow connection must be active
+ if (flags and ConnState != ConnActive) {
+ return
+ }
+
+ val deadline = _deadline
+ val reachedDeadline = deadline == now
+ var newDeadline: Long
+ var hasUpdated = false
+
+ try {
+ // Pull the source if (1) `pull` is called or (2) the timer of the source has expired
+ newDeadline = if (flags and ConnPulled != 0 || reachedDeadline) {
+ val lastPull = _lastPull
+ val delta = max(0, now - lastPull)
+
+ // Update state before calling into the outside world, so it observes a consistent state
+ _lastPull = now
+ _flags = (flags and ConnPulled.inv()) or ConnUpdateActive
+ hasUpdated = true
+
+ val duration = source.onPull(this, now, delta)
+
+ // IMPORTANT: Re-fetch the flags after the callback might have changed those
+ flags = _flags
+
+ if (duration != Long.MAX_VALUE)
+ now + duration
+ else
+ duration
+ } else {
+ deadline
+ }
+
+ // Make the new deadline available for the consumer if it has changed
+ if (newDeadline != deadline) {
+ _deadline = newDeadline
+ }
+
+ // Push to the consumer if the rate of the source has changed (after a call to `push`)
+ if (flags and ConnPushed != 0) {
+ val lastPush = _lastPush
+ val delta = max(0, now - lastPush)
+
+ // Update state before calling into the outside world, so it observes a consistent state
+ _lastPush = now
+ _flags = (flags and ConnPushed.inv()) or ConnUpdateActive
+ hasUpdated = true
+
+ logic.onPush(this, now, delta, _demand)
+
+ // IMPORTANT: Re-fetch the flags after the callback might have changed those
+ flags = _flags
+ }
+
+ // Check whether the source or consumer have tried to close the connection
+ if (flags and ConnClose != 0) {
+ hasUpdated = true
+
+ // The source has called [FlowConnection.close], so clean up the connection
+ doStopSource(now)
+
+ // IMPORTANT: Re-fetch the flags after the callback might have changed those
+ // We now also mark the connection as closed
+ flags = (_flags and ConnState.inv()) or ConnClosed
+
+ _demand = 0.0
+ newDeadline = Long.MAX_VALUE
+ }
+ } catch (cause: Throwable) {
+ hasUpdated = true
+
+ // Clean up the connection
+ doFailSource(now, cause)
+
+ // Mark the connection as closed
+ flags = (flags and ConnState.inv()) or ConnClosed
+
+ _demand = 0.0
+ newDeadline = Long.MAX_VALUE
+ }
+
+ // Check whether the connection needs to be added to the visited queue. This is the case when:
+ // (1) An update was performed (either a push or a pull)
+ // (2) Either the source or consumer want to converge, and
+ // (3) Convergence is not already pending (ConnConvergePending)
+ if (hasUpdated && flags and (ConnConvergeSource or ConnConvergeConsumer) != 0 && flags and ConnConvergePending == 0) {
+ visited.add(this)
+ flags = flags or ConnConvergePending
+ }
+
+ // Compute the new flow rate of the connection
+ // Note: _demand might be changed by [logic.onConsume], so we must re-fetch the value
+ _rate = min(_capacity, _demand)
+
+ // Indicate that no update is active anymore and flush the flags
+ _flags = flags and ConnUpdateActive.inv() and ConnUpdatePending.inv()
+
+ val pendingTimers = _pendingTimers
+
+ // Prune the head timer if this is a delayed update
+ val timer = if (!isImmediate) {
+ // Invariant: Any pending timer should only point to a future timestamp
+ val timer = pendingTimers.poll() ?: Long.MAX_VALUE
+ _timer = timer
+ timer
+ } else {
+ _timer
+ }
+
+ // Check whether we need to schedule a new timer for this connection. That is the case when:
+ // (1) The deadline is valid (not the maximum value)
+ // (2) The connection is active
+ // (3) Timers are not disabled for the source
+ // (4) The current active timer for the connection points to a later deadline
+ if (newDeadline == Long.MAX_VALUE ||
+ flags and ConnState != ConnActive ||
+ flags and ConnDisableTimers != 0 ||
+ (timer != Long.MAX_VALUE && newDeadline >= timer)
+ ) {
+ // Ignore any deadline scheduled at the maximum value
+ // This indicates that the source does not want to register a timer
+ return
+ }
+
+ // Construct a timer for the new deadline and add it to the global queue of timers
+ _timer = newDeadline
+ timerQueue.add(this, newDeadline)
+
+ // Slow-path: a timer already exists for this connection, so add it to the queue of pending timers
+ if (timer != Long.MAX_VALUE) {
+ pendingTimers.addFirst(timer)
+ }
+ }
+
+ /**
+ * This method is invoked when the system converges into a steady state.
+ */
+ fun onConverge(now: Long) {
+ try {
+ val flags = _flags
+
+ // The connection is converging now, so unset the convergence pending flag
+ _flags = flags and ConnConvergePending.inv()
+
+ // Call the source converge callback if it has enabled convergence
+ if (flags and ConnConvergeSource != 0) {
+ val delta = max(0, now - _lastSourceConvergence)
+ _lastSourceConvergence = now
+
+ source.onConverge(this, now, delta)
+ }
+
+ // Call the consumer callback if it has enabled convergence
+ if (flags and ConnConvergeConsumer != 0) {
+ val delta = max(0, now - _lastConsumerConvergence)
+ _lastConsumerConvergence = now
+
+ logic.onConverge(this, now, delta)
+ }
+ } catch (cause: Throwable) {
+ // Invoke the finish callbacks
+ doFailSource(now, cause)
+
+ // Mark the connection as closed
+ _flags = (_flags and ConnState.inv()) or ConnClosed
+ _demand = 0.0
+ _deadline = Long.MAX_VALUE
+ }
+ }
+
+ override fun toString(): String = "FlowConsumerContextImpl[capacity=$capacity,rate=$_rate]"
+
+ /**
+ * Stop the [FlowSource].
+ */
+ private fun doStopSource(now: Long) {
+ try {
+ source.onStop(this, now, max(0, now - _lastPull))
+ doFinishConsumer(now, null)
+ } catch (cause: Throwable) {
+ doFinishConsumer(now, cause)
+ }
+ }
+
+ /**
+ * Fail the [FlowSource].
+ */
+ private fun doFailSource(now: Long, cause: Throwable) {
+ try {
+ source.onStop(this, now, max(0, now - _lastPull))
+ } catch (e: Throwable) {
+ e.addSuppressed(cause)
+ doFinishConsumer(now, e)
+ }
+ }
+
+ /**
+ * Finish the consumer.
+ */
+ private fun doFinishConsumer(now: Long, cause: Throwable?) {
+ try {
+ logic.onFinish(this, now, max(0, now - _lastPush), cause)
+ } catch (e: Throwable) {
+ e.addSuppressed(cause)
+ logger.error(e) { "Uncaught exception" }
+ }
+ }
+
+ /**
+ * Schedule an immediate update for this connection.
+ */
+ private fun scheduleImmediate(now: Long, flags: Int) {
+ // In case an immediate update is already scheduled, no need to do anything
+ if (flags and ConnUpdatePending != 0) {
+ _flags = flags
+ return
+ }
+
+ // Mark the connection that there is an update pending
+ _flags = flags or ConnUpdatePending
+
+ engine.scheduleImmediate(now, this)
+ }
+}
diff --git a/opendc-simulator/opendc-simulator-flow/src/main/kotlin/org/opendc/simulator/flow/internal/FlowDeque.kt b/opendc-simulator/opendc-simulator-flow/src/main/kotlin/org/opendc/simulator/flow/internal/FlowDeque.kt
new file mode 100644
index 00000000..c6cba4b7
--- /dev/null
+++ b/opendc-simulator/opendc-simulator-flow/src/main/kotlin/org/opendc/simulator/flow/internal/FlowDeque.kt
@@ -0,0 +1,116 @@
+/*
+ * 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.flow.internal
+
+import java.util.*
+
+/**
+ * A specialized [ArrayDeque] for [FlowConsumerContextImpl] implementations.
+ */
+internal class FlowDeque(initialCapacity: Int = 256) {
+ /**
+ * The array of elements in the queue.
+ */
+ private var _elements: Array<FlowConsumerContextImpl?> = arrayOfNulls(initialCapacity)
+ private var _head = 0
+ private var _tail = 0
+
+ /**
+ * Determine whether this queue is not empty.
+ */
+ fun isNotEmpty(): Boolean {
+ return _head != _tail
+ }
+
+ /**
+ * Add the specified [ctx] to the queue.
+ */
+ fun add(ctx: FlowConsumerContextImpl) {
+ val es = _elements
+ var tail = _tail
+
+ es[tail] = ctx
+
+ tail = inc(tail, es.size)
+ _tail = tail
+
+ if (_head == tail) {
+ doubleCapacity()
+ }
+ }
+
+ /**
+ * Remove a [FlowConsumerContextImpl] from the queue or `null` if the queue is empty.
+ */
+ fun poll(): FlowConsumerContextImpl? {
+ val es = _elements
+ val head = _head
+ val ctx = es[head]
+
+ if (ctx != null) {
+ es[head] = null
+ _head = inc(head, es.size)
+ }
+
+ return ctx
+ }
+
+ /**
+ * Clear the queue.
+ */
+ fun clear() {
+ _elements.fill(null)
+ _head = 0
+ _tail = 0
+ }
+
+ private fun inc(i: Int, modulus: Int): Int {
+ var x = i
+ if (++x >= modulus) {
+ x = 0
+ }
+ return x
+ }
+
+ /**
+ * Doubles the capacity of this deque
+ */
+ private fun doubleCapacity() {
+ assert(_head == _tail)
+ val p = _head
+ val n = _elements.size
+ val r = n - p // number of elements to the right of p
+
+ val newCapacity = n shl 1
+ check(newCapacity >= 0) { "Sorry, deque too big" }
+
+ val a = arrayOfNulls<FlowConsumerContextImpl>(newCapacity)
+
+ _elements.copyInto(a, 0, p, r)
+ _elements.copyInto(a, r, 0, p)
+
+ _elements = a
+ _head = 0
+ _tail = n
+ }
+}
diff --git a/opendc-simulator/opendc-simulator-flow/src/main/kotlin/org/opendc/simulator/flow/internal/FlowEngineImpl.kt b/opendc-simulator/opendc-simulator-flow/src/main/kotlin/org/opendc/simulator/flow/internal/FlowEngineImpl.kt
new file mode 100644
index 00000000..3c79d54e
--- /dev/null
+++ b/opendc-simulator/opendc-simulator-flow/src/main/kotlin/org/opendc/simulator/flow/internal/FlowEngineImpl.kt
@@ -0,0 +1,215 @@
+/*
+ * 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.flow.internal
+
+import kotlinx.coroutines.Delay
+import kotlinx.coroutines.DisposableHandle
+import kotlinx.coroutines.InternalCoroutinesApi
+import kotlinx.coroutines.Runnable
+import org.opendc.simulator.flow.*
+import java.time.Clock
+import java.util.*
+import kotlin.coroutines.ContinuationInterceptor
+import kotlin.coroutines.CoroutineContext
+
+/**
+ * Internal implementation of the [FlowEngine] interface.
+ *
+ * @param context The coroutine context to use.
+ * @param clock The virtual simulation clock.
+ */
+internal class FlowEngineImpl(private val context: CoroutineContext, clock: Clock) : FlowEngine, Runnable {
+ /**
+ * The [Delay] instance that provides scheduled execution of [Runnable]s.
+ */
+ @OptIn(InternalCoroutinesApi::class)
+ private val delay = requireNotNull(context[ContinuationInterceptor] as? Delay) { "Invalid CoroutineDispatcher: no delay implementation" }
+
+ /**
+ * The queue of connection updates that are scheduled for immediate execution.
+ */
+ private val queue = FlowDeque()
+
+ /**
+ * A priority queue containing the connection updates to be scheduled in the future.
+ */
+ private val futureQueue = FlowTimerQueue()
+
+ /**
+ * The stack of engine invocations to occur in the future.
+ */
+ private val futureInvocations = ArrayDeque<Invocation>()
+
+ /**
+ * The systems that have been visited during the engine cycle.
+ */
+ private val visited = FlowDeque()
+
+ /**
+ * The index in the batch stack.
+ */
+ private var batchIndex = 0
+
+ /**
+ * The virtual [Clock] of this engine.
+ */
+ override val clock: Clock
+ get() = _clock
+ private val _clock: Clock = clock
+
+ /**
+ * Update the specified [ctx] synchronously.
+ */
+ fun scheduleSync(now: Long, ctx: FlowConsumerContextImpl) {
+ ctx.doUpdate(now, visited, futureQueue, isImmediate = true)
+
+ // In-case the engine is already running in the call-stack, return immediately. The changes will be picked
+ // up by the active engine.
+ if (batchIndex > 0) {
+ return
+ }
+
+ doRunEngine(now)
+ }
+
+ /**
+ * Enqueue the specified [ctx] to be updated immediately during the active engine cycle.
+ *
+ * This method should be used when the state of a flow context is invalidated/interrupted and needs to be
+ * re-computed. In case no engine is currently active, the engine will be started.
+ */
+ fun scheduleImmediate(now: Long, ctx: FlowConsumerContextImpl) {
+ queue.add(ctx)
+
+ // In-case the engine is already running in the call-stack, return immediately. The changes will be picked
+ // up by the active engine.
+ if (batchIndex > 0) {
+ return
+ }
+
+ doRunEngine(now)
+ }
+
+ override fun newContext(consumer: FlowSource, provider: FlowConsumerLogic): FlowConsumerContext = FlowConsumerContextImpl(this, consumer, provider)
+
+ override fun pushBatch() {
+ batchIndex++
+ }
+
+ override fun popBatch() {
+ try {
+ // Flush the work if the engine is not already running
+ if (batchIndex == 1 && queue.isNotEmpty()) {
+ doRunEngine(_clock.millis())
+ }
+ } finally {
+ batchIndex--
+ }
+ }
+
+ /* Runnable */
+ override fun run() {
+ val invocation = futureInvocations.poll() // Clear invocation from future invocation queue
+ doRunEngine(invocation.timestamp)
+ }
+
+ /**
+ * Run all the enqueued actions for the specified [timestamp][now].
+ */
+ private fun doRunEngine(now: Long) {
+ val queue = queue
+ val futureQueue = futureQueue
+ val futureInvocations = futureInvocations
+ val visited = visited
+
+ try {
+ // Increment batch index so synchronous calls will not launch concurrent engine invocations
+ batchIndex++
+
+ // Execute all scheduled updates at current timestamp
+ while (true) {
+ val ctx = futureQueue.poll(now) ?: break
+ ctx.doUpdate(now, visited, futureQueue, isImmediate = false)
+ }
+
+ // Repeat execution of all immediate updates until the system has converged to a steady-state
+ // We have to take into account that the onConverge callback can also trigger new actions.
+ do {
+ // Execute all immediate updates
+ while (true) {
+ val ctx = queue.poll() ?: break
+ ctx.doUpdate(now, visited, futureQueue, isImmediate = true)
+ }
+
+ while (true) {
+ val ctx = visited.poll() ?: break
+ ctx.onConverge(now)
+ }
+ } while (queue.isNotEmpty())
+ } finally {
+ // Decrement batch index to indicate no engine is active at the moment
+ batchIndex--
+ }
+
+ // Schedule an engine invocation for the next update to occur.
+ val headDeadline = futureQueue.peekDeadline()
+ if (headDeadline != Long.MAX_VALUE) {
+ trySchedule(now, futureInvocations, headDeadline)
+ }
+ }
+
+ /**
+ * Try to schedule an engine invocation at the specified [target].
+ *
+ * @param now The current virtual timestamp.
+ * @param target The virtual timestamp at which the engine invocation should happen.
+ * @param scheduled The queue of scheduled invocations.
+ */
+ private fun trySchedule(now: Long, scheduled: ArrayDeque<Invocation>, target: Long) {
+ val head = scheduled.peek()
+
+ // Only schedule a new scheduler invocation in case the target is earlier than all other pending
+ // scheduler invocations
+ if (head == null || target < head.timestamp) {
+ @OptIn(InternalCoroutinesApi::class)
+ val handle = delay.invokeOnTimeout(target - now, this, context)
+ scheduled.addFirst(Invocation(target, handle))
+ }
+ }
+
+ /**
+ * A future engine invocation.
+ *
+ * This class is used to keep track of the future engine invocations created using the [Delay] instance. In case
+ * the invocation is not needed anymore, it can be cancelled via [cancel].
+ */
+ private class Invocation(
+ @JvmField val timestamp: Long,
+ @JvmField val handle: DisposableHandle
+ ) {
+ /**
+ * Cancel the engine invocation.
+ */
+ fun cancel() = handle.dispose()
+ }
+}
diff --git a/opendc-simulator/opendc-simulator-flow/src/main/kotlin/org/opendc/simulator/flow/internal/FlowTimerQueue.kt b/opendc-simulator/opendc-simulator-flow/src/main/kotlin/org/opendc/simulator/flow/internal/FlowTimerQueue.kt
new file mode 100644
index 00000000..22a390e6
--- /dev/null
+++ b/opendc-simulator/opendc-simulator-flow/src/main/kotlin/org/opendc/simulator/flow/internal/FlowTimerQueue.kt
@@ -0,0 +1,195 @@
+/*
+ * 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.flow.internal
+
+/**
+ * Specialized priority queue for flow timers.
+ */
+internal class FlowTimerQueue(initialCapacity: Int = 256) {
+ /**
+ * The binary heap of deadlines.
+ */
+ private var _deadlines = LongArray(initialCapacity) { Long.MIN_VALUE }
+
+ /**
+ * The binary heap of [FlowConsumerContextImpl]s.
+ */
+ private var _pending = arrayOfNulls<FlowConsumerContextImpl>(initialCapacity)
+
+ /**
+ * The number of elements in the priority queue.
+ */
+ private var size = 0
+
+ /**
+ * Register a timer for [ctx] with [deadline].
+ */
+ fun add(ctx: FlowConsumerContextImpl, deadline: Long) {
+ val i = size
+ val deadlines = _deadlines
+ if (i >= deadlines.size) {
+ grow()
+ }
+
+ siftUp(deadlines, _pending, i, ctx, deadline)
+
+ size = i + 1
+ }
+
+ /**
+ * Update all pending [FlowConsumerContextImpl]s at the timestamp [now].
+ */
+ fun poll(now: Long): FlowConsumerContextImpl? {
+ if (size == 0) {
+ return null
+ }
+
+ val deadlines = _deadlines
+ val deadline = deadlines[0]
+
+ if (now < deadline) {
+ return null
+ }
+
+ val pending = _pending
+ val res = pending[0]
+ val s = --size
+
+ val nextDeadline = deadlines[s]
+ val next = pending[s]!!
+
+ // Clear the last element of the queue
+ pending[s] = null
+ deadlines[s] = Long.MIN_VALUE
+
+ if (s != 0) {
+ siftDown(deadlines, pending, next, nextDeadline)
+ }
+
+ return res
+ }
+
+ /**
+ * Find the earliest deadline in the queue.
+ */
+ fun peekDeadline(): Long {
+ return if (size == 0) Long.MAX_VALUE else _deadlines[0]
+ }
+
+ /**
+ * Increases the capacity of the array.
+ */
+ private fun grow() {
+ val oldCapacity = _deadlines.size
+ // Double size if small; else grow by 50%
+ val newCapacity = oldCapacity + if (oldCapacity < 64) oldCapacity + 2 else oldCapacity shr 1
+
+ _deadlines = _deadlines.copyOf(newCapacity)
+ _pending = _pending.copyOf(newCapacity)
+ }
+
+ /**
+ * Insert item [ctx] at position [pos], maintaining heap invariant by promoting [ctx] up the tree until it is
+ * greater than or equal to its parent, or is the root.
+ *
+ * @param deadlines The heap of deadlines.
+ * @param pending The heap of contexts.
+ * @param pos The position to fill.
+ * @param ctx The [FlowConsumerContextImpl] to insert.
+ * @param deadline The deadline of the context.
+ */
+ private fun siftUp(
+ deadlines: LongArray,
+ pending: Array<FlowConsumerContextImpl?>,
+ pos: Int,
+ ctx: FlowConsumerContextImpl,
+ deadline: Long
+ ) {
+ var k = pos
+
+ while (k > 0) {
+ val parent = (k - 1) ushr 1
+ val parentDeadline = deadlines[parent]
+
+ if (deadline >= parentDeadline) {
+ break
+ }
+
+ deadlines[k] = parentDeadline
+ pending[k] = pending[parent]
+
+ k = parent
+ }
+
+ deadlines[k] = deadline
+ pending[k] = ctx
+ }
+
+ /**
+ * Inserts [ctx] at the top, maintaining heap invariant by demoting [ctx] down the tree repeatedly until it
+ * is less than or equal to its children or is a leaf.
+ *
+ * @param deadlines The heap of deadlines.
+ * @param pending The heap of contexts.
+ * @param ctx The [FlowConsumerContextImpl] to insert.
+ * @param deadline The deadline of the context.
+ */
+ private fun siftDown(
+ deadlines: LongArray,
+ pending: Array<FlowConsumerContextImpl?>,
+ ctx: FlowConsumerContextImpl,
+ deadline: Long
+ ) {
+ var k = 0
+ val size = size
+ val half = size ushr 1
+
+ while (k < half) {
+ var child = (k shl 1) + 1
+
+ var childDeadline = deadlines[child]
+ val right = child + 1
+
+ if (right < size) {
+ val rightDeadline = deadlines[right]
+
+ if (childDeadline > rightDeadline) {
+ child = right
+ childDeadline = rightDeadline
+ }
+ }
+
+ if (deadline <= childDeadline) {
+ break
+ }
+
+ deadlines[k] = childDeadline
+ pending[k] = pending[child]
+
+ k = child
+ }
+
+ deadlines[k] = deadline
+ pending[k] = ctx
+ }
+}
diff --git a/opendc-simulator/opendc-simulator-flow/src/main/kotlin/org/opendc/simulator/flow/internal/MutableFlowCounters.kt b/opendc-simulator/opendc-simulator-flow/src/main/kotlin/org/opendc/simulator/flow/internal/MutableFlowCounters.kt
new file mode 100644
index 00000000..d990dc61
--- /dev/null
+++ b/opendc-simulator/opendc-simulator-flow/src/main/kotlin/org/opendc/simulator/flow/internal/MutableFlowCounters.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.flow.internal
+
+import org.opendc.simulator.flow.FlowCounters
+
+/**
+ * Mutable implementation of the [FlowCounters] interface.
+ */
+public class MutableFlowCounters : FlowCounters {
+ override val demand: Double
+ get() = _counters[0]
+ override val actual: Double
+ get() = _counters[1]
+ override val remaining: Double
+ get() = _counters[2]
+ override val interference: Double
+ get() = _counters[3]
+ private val _counters = DoubleArray(4)
+
+ override fun reset() {
+ _counters.fill(0.0)
+ }
+
+ public fun increment(demand: Double, actual: Double, remaining: Double, interference: Double) {
+ val counters = _counters
+ counters[0] += demand
+ counters[1] += actual
+ counters[2] += remaining
+ counters[3] += interference
+ }
+
+ override fun toString(): String {
+ return "FlowCounters[demand=$demand,actual=$actual,remaining=$remaining,interference=$interference]"
+ }
+}
diff --git a/opendc-simulator/opendc-simulator-flow/src/main/kotlin/org/opendc/simulator/flow/mux/FlowMultiplexer.kt b/opendc-simulator/opendc-simulator-flow/src/main/kotlin/org/opendc/simulator/flow/mux/FlowMultiplexer.kt
new file mode 100644
index 00000000..04ba7f21
--- /dev/null
+++ b/opendc-simulator/opendc-simulator-flow/src/main/kotlin/org/opendc/simulator/flow/mux/FlowMultiplexer.kt
@@ -0,0 +1,100 @@
+/*
+ * 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.flow.mux
+
+import org.opendc.simulator.flow.FlowConsumer
+import org.opendc.simulator.flow.FlowCounters
+import org.opendc.simulator.flow.FlowSource
+import org.opendc.simulator.flow.interference.InterferenceKey
+
+/**
+ * A [FlowMultiplexer] enables multiplexing multiple [FlowSource]s over possibly multiple [FlowConsumer]s.
+ */
+public interface FlowMultiplexer {
+ /**
+ * The inputs of the multiplexer that can be used to consume sources.
+ */
+ public val inputs: Set<FlowConsumer>
+
+ /**
+ * The outputs of the multiplexer over which the flows will be distributed.
+ */
+ public val outputs: Set<FlowSource>
+
+ /**
+ * The actual processing rate of the multiplexer.
+ */
+ public val rate: Double
+
+ /**
+ * The demanded processing rate of the input.
+ */
+ public val demand: Double
+
+ /**
+ * The capacity of the outputs.
+ */
+ public val capacity: Double
+
+ /**
+ * The flow counters to track the flow metrics of all multiplexer inputs.
+ */
+ public val counters: FlowCounters
+
+ /**
+ * Create a new input on this multiplexer.
+ *
+ * @param key The key of the interference member to which the input belongs.
+ */
+ public fun newInput(key: InterferenceKey? = null): FlowConsumer
+
+ /**
+ * Remove [input] from this multiplexer.
+ */
+ public fun removeInput(input: FlowConsumer)
+
+ /**
+ * Create a new output on this multiplexer.
+ */
+ public fun newOutput(): FlowSource
+
+ /**
+ * Remove [output] from this multiplexer.
+ */
+ public fun removeOutput(output: FlowSource)
+
+ /**
+ * Clear all inputs and outputs from the multiplexer.
+ */
+ public fun clear()
+
+ /**
+ * Clear the inputs of the multiplexer.
+ */
+ public fun clearInputs()
+
+ /**
+ * Clear the outputs of the multiplexer.
+ */
+ public fun clearOutputs()
+}
diff --git a/opendc-simulator/opendc-simulator-flow/src/main/kotlin/org/opendc/simulator/flow/mux/ForwardingFlowMultiplexer.kt b/opendc-simulator/opendc-simulator-flow/src/main/kotlin/org/opendc/simulator/flow/mux/ForwardingFlowMultiplexer.kt
new file mode 100644
index 00000000..125d10fe
--- /dev/null
+++ b/opendc-simulator/opendc-simulator-flow/src/main/kotlin/org/opendc/simulator/flow/mux/ForwardingFlowMultiplexer.kt
@@ -0,0 +1,154 @@
+/*
+ * 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.flow.mux
+
+import org.opendc.simulator.flow.*
+import org.opendc.simulator.flow.interference.InterferenceKey
+import java.util.ArrayDeque
+
+/**
+ * A [FlowMultiplexer] implementation that allocates inputs to the outputs of the multiplexer exclusively. This means
+ * that a single input is directly connected to an output and that the multiplexer can only support as many
+ * inputs as outputs.
+ *
+ * @param engine The [FlowEngine] driving the simulation.
+ */
+public class ForwardingFlowMultiplexer(private val engine: FlowEngine) : FlowMultiplexer {
+ override val inputs: Set<FlowConsumer>
+ get() = _inputs
+ private val _inputs = mutableSetOf<Input>()
+
+ override val outputs: Set<FlowSource>
+ get() = _outputs
+ private val _outputs = mutableSetOf<Output>()
+ private val _availableOutputs = ArrayDeque<Output>()
+
+ override val counters: FlowCounters = object : FlowCounters {
+ override val demand: Double
+ get() = _outputs.sumOf { it.forwarder.counters.demand }
+ override val actual: Double
+ get() = _outputs.sumOf { it.forwarder.counters.actual }
+ override val remaining: Double
+ get() = _outputs.sumOf { it.forwarder.counters.remaining }
+ override val interference: Double
+ get() = _outputs.sumOf { it.forwarder.counters.interference }
+
+ override fun reset() {
+ for (output in _outputs) {
+ output.forwarder.counters.reset()
+ }
+ }
+
+ override fun toString(): String = "FlowCounters[demand=$demand,actual=$actual,remaining=$remaining]"
+ }
+
+ override val rate: Double
+ get() = _outputs.sumOf { it.forwarder.rate }
+
+ override val demand: Double
+ get() = _outputs.sumOf { it.forwarder.demand }
+
+ override val capacity: Double
+ get() = _outputs.sumOf { it.forwarder.capacity }
+
+ override fun newInput(key: InterferenceKey?): FlowConsumer {
+ val output = checkNotNull(_availableOutputs.poll()) { "No capacity to serve request" }
+ val input = Input(output)
+ _inputs += input
+ return input
+ }
+
+ override fun removeInput(input: FlowConsumer) {
+ if (!_inputs.remove(input)) {
+ return
+ }
+
+ val output = (input as Input).output
+ output.forwarder.cancel()
+ _availableOutputs += output
+ }
+
+ override fun newOutput(): FlowSource {
+ val forwarder = FlowForwarder(engine)
+ val output = Output(forwarder)
+
+ _outputs += output
+ return output
+ }
+
+ override fun removeOutput(output: FlowSource) {
+ if (!_outputs.remove(output)) {
+ return
+ }
+
+ val forwarder = (output as Output).forwarder
+ forwarder.close()
+ }
+
+ override fun clearInputs() {
+ for (input in _inputs) {
+ val output = input.output
+ output.forwarder.cancel()
+ _availableOutputs += output
+ }
+
+ _inputs.clear()
+ }
+
+ override fun clearOutputs() {
+ for (output in _outputs) {
+ output.forwarder.cancel()
+ }
+ _outputs.clear()
+ _availableOutputs.clear()
+ }
+
+ override fun clear() {
+ clearOutputs()
+ clearInputs()
+ }
+
+ /**
+ * An input on the multiplexer.
+ */
+ private inner class Input(@JvmField val output: Output) : FlowConsumer by output.forwarder {
+ override fun toString(): String = "ForwardingFlowMultiplexer.Input"
+ }
+
+ /**
+ * An output on the multiplexer.
+ */
+ private inner class Output(@JvmField val forwarder: FlowForwarder) : FlowSource by forwarder {
+ override fun onStart(conn: FlowConnection, now: Long) {
+ _availableOutputs += this
+ forwarder.onStart(conn, now)
+ }
+
+ override fun onStop(conn: FlowConnection, now: Long, delta: Long) {
+ forwarder.cancel()
+ forwarder.onStop(conn, now, delta)
+ }
+
+ override fun toString(): String = "ForwardingFlowMultiplexer.Output"
+ }
+}
diff --git a/opendc-simulator/opendc-simulator-flow/src/main/kotlin/org/opendc/simulator/flow/mux/MaxMinFlowMultiplexer.kt b/opendc-simulator/opendc-simulator-flow/src/main/kotlin/org/opendc/simulator/flow/mux/MaxMinFlowMultiplexer.kt
new file mode 100644
index 00000000..a0fb8a4e
--- /dev/null
+++ b/opendc-simulator/opendc-simulator-flow/src/main/kotlin/org/opendc/simulator/flow/mux/MaxMinFlowMultiplexer.kt
@@ -0,0 +1,789 @@
+/*
+ * 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.flow.mux
+
+import org.opendc.simulator.flow.*
+import org.opendc.simulator.flow.interference.InterferenceDomain
+import org.opendc.simulator.flow.interference.InterferenceKey
+import org.opendc.simulator.flow.internal.D_MS_TO_S
+import org.opendc.simulator.flow.internal.MutableFlowCounters
+import kotlin.math.max
+import kotlin.math.min
+
+/**
+ * A [FlowMultiplexer] implementation that multiplexes flows over the available outputs using max-min fair sharing.
+ *
+ * @param engine The [FlowEngine] to drive the flow simulation.
+ * @param parent The parent flow system of the multiplexer.
+ * @param interferenceDomain The interference domain of the multiplexer.
+ */
+public class MaxMinFlowMultiplexer(
+ private val engine: FlowEngine,
+ parent: FlowConvergenceListener? = null,
+ private val interferenceDomain: InterferenceDomain? = null
+) : FlowMultiplexer {
+ /**
+ * The inputs of the multiplexer.
+ */
+ override val inputs: Set<FlowConsumer>
+ get() = _inputs
+ private val _inputs = mutableSetOf<Input>()
+
+ /**
+ * The outputs of the multiplexer.
+ */
+ override val outputs: Set<FlowSource>
+ get() = _outputs
+ private val _outputs = mutableSetOf<Output>()
+
+ /**
+ * The flow counters of this multiplexer.
+ */
+ public override val counters: FlowCounters
+ get() = scheduler.counters
+
+ /**
+ * The actual processing rate of the multiplexer.
+ */
+ public override val rate: Double
+ get() = scheduler.rate
+
+ /**
+ * The demanded processing rate of the input.
+ */
+ public override val demand: Double
+ get() = scheduler.demand
+
+ /**
+ * The capacity of the outputs.
+ */
+ public override val capacity: Double
+ get() = scheduler.capacity
+
+ /**
+ * The [Scheduler] instance of this multiplexer.
+ */
+ private val scheduler = Scheduler(engine, parent)
+
+ override fun newInput(key: InterferenceKey?): FlowConsumer {
+ val provider = Input(engine, scheduler, interferenceDomain, key, scheduler.capacity)
+ _inputs.add(provider)
+ return provider
+ }
+
+ override fun removeInput(input: FlowConsumer) {
+ if (!_inputs.remove(input)) {
+ return
+ }
+ // This cast should always succeed since only `Input` instances should be added to `_inputs`
+ (input as Input).close()
+ }
+
+ override fun newOutput(): FlowSource {
+ val output = Output(scheduler)
+ _outputs.add(output)
+ return output
+ }
+
+ override fun removeOutput(output: FlowSource) {
+ if (!_outputs.remove(output)) {
+ return
+ }
+
+ // This cast should always succeed since only `Output` instances should be added to `_outputs`
+ (output as Output).cancel()
+ }
+
+ override fun clearInputs() {
+ for (input in _inputs) {
+ input.cancel()
+ }
+ _inputs.clear()
+ }
+
+ override fun clearOutputs() {
+ for (output in _outputs) {
+ output.cancel()
+ }
+ _outputs.clear()
+ }
+
+ override fun clear() {
+ clearOutputs()
+ clearInputs()
+ }
+
+ /**
+ * Helper class containing the scheduler state.
+ */
+ private class Scheduler(engine: FlowEngine, private val parent: FlowConvergenceListener?) {
+ /**
+ * The flow counters of this scheduler.
+ */
+ @JvmField val counters = MutableFlowCounters()
+
+ /**
+ * The flow rate of the multiplexer.
+ */
+ @JvmField var rate = 0.0
+
+ /**
+ * The demand for the multiplexer.
+ */
+ @JvmField var demand = 0.0
+
+ /**
+ * The capacity of the multiplexer.
+ */
+ @JvmField var capacity = 0.0
+
+ /**
+ * An [Output] that is used to activate the scheduler.
+ */
+ @JvmField var activationOutput: Output? = null
+
+ /**
+ * The active inputs registered with the scheduler.
+ */
+ private val _activeInputs = mutableListOf<Input>()
+
+ /**
+ * An array containing the active inputs, which is used to reduce the overhead of an [ArrayList].
+ */
+ private var _inputArray = emptyArray<Input>()
+
+ /**
+ * The active outputs registered with the scheduler.
+ */
+ private val _activeOutputs = mutableListOf<Output>()
+
+ /**
+ * Flag to indicate that the scheduler is active.
+ */
+ private var _schedulerActive = false
+ private var _lastSchedulerCycle = Long.MAX_VALUE
+
+ /**
+ * The last convergence timestamp and the input.
+ */
+ private var _lastConverge: Long = Long.MIN_VALUE
+ private var _lastConvergeInput: Input? = null
+
+ /**
+ * The simulation clock.
+ */
+ private val _clock = engine.clock
+
+ /**
+ * Register the specified [input] to this scheduler.
+ */
+ fun registerInput(input: Input) {
+ _activeInputs.add(input)
+ _inputArray = _activeInputs.toTypedArray()
+
+ val hasActivationOutput = activationOutput != null
+
+ // Disable timers and convergence of the source if one of the output manages it
+ input.shouldConsumerConverge = !hasActivationOutput
+ input.enableTimers = !hasActivationOutput
+ input.capacity = capacity
+
+ trigger(_clock.millis())
+ }
+
+ /**
+ * De-register the specified [input] from this scheduler.
+ */
+ fun deregisterInput(input: Input, now: Long) {
+ // Assign a new input responsible for handling the convergence events
+ if (_lastConvergeInput == input) {
+ _lastConvergeInput = null
+ }
+
+ _activeInputs.remove(input)
+
+ // Re-run scheduler to distribute new load
+ trigger(now)
+ }
+
+ /**
+ * This method is invoked when one of the inputs converges.
+ */
+ fun convergeInput(input: Input, now: Long) {
+
+ val lastConverge = _lastConverge
+ val lastConvergeInput = _lastConvergeInput
+ val parent = parent
+
+ if (parent != null && (now > lastConverge || lastConvergeInput == null || lastConvergeInput == input)) {
+ _lastConverge = now
+ _lastConvergeInput = input
+
+ parent.onConverge(now, max(0, now - lastConverge))
+ }
+ }
+
+ /**
+ * Register the specified [output] to this scheduler.
+ */
+ fun registerOutput(output: Output) {
+ _activeOutputs.add(output)
+
+ updateCapacity()
+ updateActivationOutput()
+ }
+
+ /**
+ * De-register the specified [output] from this scheduler.
+ */
+ fun deregisterOutput(output: Output, now: Long) {
+ _activeOutputs.remove(output)
+ updateCapacity()
+
+ trigger(now)
+ }
+
+ /**
+ * This method is invoked when one of the outputs converges.
+ */
+ fun convergeOutput(output: Output, now: Long) {
+ val lastConverge = _lastConverge
+ val parent = parent
+
+ if (parent != null) {
+ _lastConverge = now
+
+ parent.onConverge(now, max(0, now - lastConverge))
+ }
+
+ if (!output.isActive) {
+ output.isActivationOutput = false
+ updateActivationOutput()
+ }
+ }
+
+ /**
+ * Trigger the scheduler of the multiplexer.
+ *
+ * @param now The current virtual timestamp of the simulation.
+ */
+ fun trigger(now: Long) {
+ if (_schedulerActive) {
+ // No need to trigger the scheduler in case it is already active
+ return
+ }
+
+ val activationOutput = activationOutput
+
+ // We can run the scheduler in two ways:
+ // (1) We can pull one of the multiplexer's outputs. This allows us to cascade multiple pushes by the input
+ // into a single scheduling cycle, but is slower in case of a few changes at the same timestamp.
+ // (2) We run the scheduler directly from this method call. This is the fastest approach when there are only
+ // a few inputs and little changes at the same timestamp.
+ // We always pick for option (1) unless there are no outputs available.
+ if (activationOutput != null) {
+ activationOutput.pull(now)
+ return
+ } else {
+ runScheduler(now)
+ }
+ }
+
+ /**
+ * Synchronously run the scheduler of the multiplexer.
+ */
+ fun runScheduler(now: Long): Long {
+ val lastSchedulerCycle = _lastSchedulerCycle
+ _lastSchedulerCycle = now
+
+ val delta = max(0, now - lastSchedulerCycle)
+
+ return try {
+ _schedulerActive = true
+ doRunScheduler(now, delta)
+ } finally {
+ _schedulerActive = false
+ }
+ }
+
+ /**
+ * Recompute the capacity of the multiplexer.
+ */
+ fun updateCapacity() {
+ val newCapacity = _activeOutputs.sumOf(Output::capacity)
+
+ // No-op if the capacity is unchanged
+ if (capacity == newCapacity) {
+ return
+ }
+
+ capacity = newCapacity
+
+ for (input in _activeInputs) {
+ input.capacity = newCapacity
+ }
+
+ // Sort outputs by their capacity
+ _activeOutputs.sort()
+ }
+
+ /**
+ * Updates the output that is used for scheduler activation.
+ */
+ private fun updateActivationOutput() {
+ val output = _activeOutputs.firstOrNull()
+ activationOutput = output
+
+ if (output != null) {
+ output.isActivationOutput = true
+ }
+
+ val hasActivationOutput = output != null
+
+ for (input in _activeInputs) {
+ input.shouldConsumerConverge = !hasActivationOutput
+ input.enableTimers = !hasActivationOutput
+ }
+ }
+
+ /**
+ * Schedule the inputs over the outputs.
+ *
+ * @return The deadline after which a new scheduling cycle should start.
+ */
+ private fun doRunScheduler(now: Long, delta: Long): Long {
+ val activeInputs = _activeInputs
+ val activeOutputs = _activeOutputs
+ var inputArray = _inputArray
+ var inputSize = _inputArray.size
+
+ // Update the counters of the scheduler
+ updateCounters(delta)
+
+ // If there is no work yet, mark the inputs as idle.
+ if (inputSize == 0) {
+ demand = 0.0
+ rate = 0.0
+ return Long.MAX_VALUE
+ }
+
+ val capacity = capacity
+ var availableCapacity = capacity
+ var deadline = Long.MAX_VALUE
+ var demand = 0.0
+ var shouldRebuild = false
+
+ // Pull in the work of the inputs
+ for (i in 0 until inputSize) {
+ val input = inputArray[i]
+
+ input.pullSync(now)
+
+ // Remove inputs that have finished
+ if (!input.isActive) {
+ input.actualRate = 0.0
+ shouldRebuild = true
+ } else {
+ demand += input.limit
+ deadline = min(deadline, input.deadline)
+ }
+ }
+
+ // Slow-path: Rebuild the input array based on the (apparently) updated `activeInputs`
+ if (shouldRebuild) {
+ inputArray = activeInputs.toTypedArray()
+ inputSize = inputArray.size
+ _inputArray = inputArray
+ }
+
+ val rate = if (demand > capacity) {
+ // If the demand is higher than the capacity, we need use max-min fair sharing to distribute the
+ // constrained capacity across the inputs.
+
+ // Sort in-place the inputs based on their pushed flow.
+ // Profiling shows that it is faster than maintaining some kind of sorted set.
+ inputArray.sort()
+
+ // Divide the available output capacity fairly over the inputs using max-min fair sharing
+ for (i in 0 until inputSize) {
+ val input = inputArray[i]
+ val availableShare = availableCapacity / (inputSize - i)
+ val grantedRate = min(input.allowedRate, availableShare)
+
+ availableCapacity -= grantedRate
+ input.actualRate = grantedRate
+ }
+
+ capacity - availableCapacity
+ } else {
+ demand
+ }
+
+ this.demand = demand
+ if (this.rate != rate) {
+ // Only update the outputs if the output rate has changed
+ this.rate = rate
+
+ // Divide the requests over the available capacity of the input resources fairly
+ for (i in activeOutputs.indices) {
+ val output = activeOutputs[i]
+ val inputCapacity = output.capacity
+ val fraction = inputCapacity / capacity
+ val grantedSpeed = rate * fraction
+
+ output.push(grantedSpeed)
+ }
+ }
+
+ return deadline
+ }
+
+ /**
+ * The previous capacity of the multiplexer.
+ */
+ private var _previousCapacity = 0.0
+
+ /**
+ * Update the counters of the scheduler.
+ */
+ private fun updateCounters(delta: Long) {
+ val previousCapacity = _previousCapacity
+ _previousCapacity = capacity
+
+ if (delta <= 0) {
+ return
+ }
+
+ val deltaS = delta * D_MS_TO_S
+ val demand = demand
+ val rate = rate
+
+ counters.increment(
+ demand = demand * deltaS,
+ actual = rate * deltaS,
+ remaining = (previousCapacity - rate) * deltaS,
+ interference = 0.0
+ )
+ }
+ }
+
+ /**
+ * An internal [FlowConsumer] implementation for multiplexer inputs.
+ */
+ private class Input(
+ private val engine: FlowEngine,
+ private val scheduler: Scheduler,
+ private val interferenceDomain: InterferenceDomain?,
+ @JvmField val key: InterferenceKey?,
+ initialCapacity: Double,
+ ) : FlowConsumer, FlowConsumerLogic, Comparable<Input> {
+ /**
+ * A flag to indicate that the consumer is active.
+ */
+ override val isActive: Boolean
+ get() = _ctx != null
+
+ /**
+ * The demand of the consumer.
+ */
+ override val demand: Double
+ get() = limit
+
+ /**
+ * The processing rate of the consumer.
+ */
+ override val rate: Double
+ get() = actualRate
+
+ /**
+ * The capacity of the input.
+ */
+ override var capacity: Double
+ get() = _capacity
+ set(value) {
+ allowedRate = min(limit, value)
+ _capacity = value
+ _ctx?.capacity = value
+ }
+ private var _capacity = initialCapacity
+
+ /**
+ * The flow counters to track the flow metrics of the consumer.
+ */
+ override val counters: FlowCounters
+ get() = _counters
+ private val _counters = MutableFlowCounters()
+
+ /**
+ * A flag to enable timers for the input.
+ */
+ var enableTimers: Boolean = true
+ set(value) {
+ field = value
+ _ctx?.enableTimers = value
+ }
+
+ /**
+ * A flag to control whether the input should converge.
+ */
+ var shouldConsumerConverge: Boolean = true
+ set(value) {
+ field = value
+ _ctx?.shouldConsumerConverge = value
+ }
+
+ /**
+ * The requested limit.
+ */
+ @JvmField var limit: Double = 0.0
+
+ /**
+ * The actual processing speed.
+ */
+ @JvmField var actualRate: Double = 0.0
+
+ /**
+ * The processing rate that is allowed by the model constraints.
+ */
+ @JvmField var allowedRate: Double = 0.0
+
+ /**
+ * The deadline of the input.
+ */
+ val deadline: Long
+ get() = _ctx?.deadline ?: Long.MAX_VALUE
+
+ /**
+ * The [FlowConsumerContext] that is currently running.
+ */
+ private var _ctx: FlowConsumerContext? = null
+
+ /**
+ * A flag to indicate that the input is closed.
+ */
+ private var _isClosed: Boolean = false
+
+ /**
+ * Close the input.
+ *
+ * This method is invoked when the user removes an input from the switch.
+ */
+ fun close() {
+ _isClosed = true
+ cancel()
+ }
+
+ /**
+ * Pull the source if necessary.
+ */
+ fun pullSync(now: Long) {
+ _ctx?.pullSync(now)
+ }
+
+ /* FlowConsumer */
+ override fun startConsumer(source: FlowSource) {
+ check(!_isClosed) { "Cannot re-use closed input" }
+ check(_ctx == null) { "Consumer is in invalid state" }
+
+ val ctx = engine.newContext(source, this)
+ _ctx = ctx
+
+ ctx.capacity = capacity
+ scheduler.registerInput(this)
+
+ ctx.start()
+ }
+
+ override fun pull() {
+ _ctx?.pull()
+ }
+
+ override fun cancel() {
+ _ctx?.close()
+ }
+
+ /* FlowConsumerLogic */
+ override fun onPush(
+ ctx: FlowConsumerContext,
+ now: Long,
+ delta: Long,
+ rate: Double
+ ) {
+ doUpdateCounters(delta)
+
+ val allowed = min(rate, capacity)
+ limit = rate
+ actualRate = allowed
+ allowedRate = allowed
+
+ scheduler.trigger(now)
+ }
+
+ override fun onFinish(ctx: FlowConsumerContext, now: Long, delta: Long, cause: Throwable?) {
+ doUpdateCounters(delta)
+
+ limit = 0.0
+ actualRate = 0.0
+ allowedRate = 0.0
+
+ scheduler.deregisterInput(this, now)
+
+ _ctx = null
+ }
+
+ override fun onConverge(ctx: FlowConsumerContext, now: Long, delta: Long) {
+ scheduler.convergeInput(this, now)
+ }
+
+ /* Comparable */
+ override fun compareTo(other: Input): Int = allowedRate.compareTo(other.allowedRate)
+
+ /**
+ * Helper method to update the flow counters of the multiplexer.
+ */
+ private fun doUpdateCounters(delta: Long) {
+ if (delta <= 0L) {
+ return
+ }
+
+ // Compute the performance penalty due to flow interference
+ val perfScore = if (interferenceDomain != null) {
+ val load = scheduler.rate / scheduler.capacity
+ interferenceDomain.apply(key, load)
+ } else {
+ 1.0
+ }
+
+ val actualRate = actualRate
+
+ val deltaS = delta * D_MS_TO_S
+ val demand = limit * deltaS
+ val actual = actualRate * deltaS
+ val remaining = (_capacity - actualRate) * deltaS
+ val interference = actual * max(0.0, 1 - perfScore)
+
+ _counters.increment(demand, actual, remaining, interference)
+ scheduler.counters.increment(0.0, 0.0, 0.0, interference)
+ }
+ }
+
+ /**
+ * An internal [FlowSource] implementation for multiplexer outputs.
+ */
+ private class Output(private val scheduler: Scheduler) : FlowSource, Comparable<Output> {
+ /**
+ * The active [FlowConnection] of this source.
+ */
+ private var _conn: FlowConnection? = null
+
+ /**
+ * The capacity of this output.
+ */
+ @JvmField var capacity: Double = 0.0
+
+ /**
+ * A flag to indicate that this output is the activation output.
+ */
+ var isActivationOutput: Boolean
+ get() = _isActivationOutput
+ set(value) {
+ _isActivationOutput = value
+ _conn?.shouldSourceConverge = value
+ }
+ private var _isActivationOutput: Boolean = false
+
+ /**
+ * A flag to indicate that the output is active.
+ */
+ @JvmField var isActive = false
+
+ /**
+ * Push the specified rate to the consumer.
+ */
+ fun push(rate: Double) {
+ _conn?.push(rate)
+ }
+
+ /**
+ * Cancel this output.
+ */
+ fun cancel() {
+ _conn?.close()
+ }
+
+ /**
+ * Pull this output.
+ */
+ fun pull(now: Long) {
+ _conn?.pull(now)
+ }
+
+ override fun onStart(conn: FlowConnection, now: Long) {
+ assert(_conn == null) { "Source running concurrently" }
+ _conn = conn
+ capacity = conn.capacity
+ isActive = true
+
+ scheduler.registerOutput(this)
+ }
+
+ override fun onStop(conn: FlowConnection, now: Long, delta: Long) {
+ _conn = null
+ capacity = 0.0
+ isActive = false
+
+ scheduler.deregisterOutput(this, now)
+ }
+
+ override fun onPull(conn: FlowConnection, now: Long, delta: Long): Long {
+ val capacity = capacity
+ if (capacity != conn.capacity) {
+ this.capacity = capacity
+ scheduler.updateCapacity()
+ }
+
+ return if (_isActivationOutput) {
+ // If this output is the activation output, synchronously run the scheduler and return the new deadline
+ val deadline = scheduler.runScheduler(now)
+ if (deadline == Long.MAX_VALUE)
+ deadline
+ else
+ deadline - now
+ } else {
+ // Output is not the activation output, so trigger activation output and do not install timer for this
+ // output (by returning `Long.MAX_VALUE`)
+ scheduler.trigger(now)
+
+ Long.MAX_VALUE
+ }
+ }
+
+ override fun onConverge(conn: FlowConnection, now: Long, delta: Long) {
+ if (_isActivationOutput) {
+ scheduler.convergeOutput(this, now)
+ }
+ }
+
+ override fun compareTo(other: Output): Int = capacity.compareTo(other.capacity)
+ }
+}
diff --git a/opendc-simulator/opendc-simulator-resources/src/main/kotlin/org/opendc/simulator/resources/consumer/SimWorkConsumer.kt b/opendc-simulator/opendc-simulator-flow/src/main/kotlin/org/opendc/simulator/flow/source/FixedFlowSource.kt
index faa693c4..d9779c6a 100644
--- a/opendc-simulator/opendc-simulator-resources/src/main/kotlin/org/opendc/simulator/resources/consumer/SimWorkConsumer.kt
+++ b/opendc-simulator/opendc-simulator-flow/src/main/kotlin/org/opendc/simulator/flow/source/FixedFlowSource.kt
@@ -20,39 +20,38 @@
* SOFTWARE.
*/
-package org.opendc.simulator.resources.consumer
+package org.opendc.simulator.flow.source
-import org.opendc.simulator.resources.SimResourceCommand
-import org.opendc.simulator.resources.SimResourceConsumer
-import org.opendc.simulator.resources.SimResourceContext
+import org.opendc.simulator.flow.FlowConnection
+import org.opendc.simulator.flow.FlowSource
+import kotlin.math.roundToLong
/**
- * A [SimResourceConsumer] that consumes the specified amount of work at the specified utilization.
+ * A [FlowSource] that contains a fixed [amount] and is pushed with a given [utilization].
*/
-public class SimWorkConsumer(
- private val work: Double,
- private val utilization: Double
-) : SimResourceConsumer {
+public class FixedFlowSource(private val amount: Double, private val utilization: Double) : FlowSource {
init {
- require(work >= 0.0) { "Work must be positive" }
- require(utilization > 0.0 && utilization <= 1.0) { "Utilization must be in (0, 1]" }
+ require(amount >= 0.0) { "Amount must be positive" }
+ require(utilization > 0.0) { "Utilization must be positive" }
}
- private var isFirst = true
+ private var remainingAmount = amount
- override fun onNext(ctx: SimResourceContext): SimResourceCommand {
- val limit = ctx.capacity * utilization
- val work = if (isFirst) {
- isFirst = false
- work
- } else {
- ctx.remainingWork
- }
- return if (work > 0.0) {
- SimResourceCommand.Consume(work, limit)
+ override fun onPull(conn: FlowConnection, now: Long, delta: Long): Long {
+ val consumed = conn.rate * delta / 1000.0
+ val limit = conn.capacity * utilization
+
+ remainingAmount -= consumed
+
+ val duration = (remainingAmount / limit * 1000).roundToLong()
+
+ return if (duration > 0) {
+ conn.push(limit)
+ duration
} else {
- SimResourceCommand.Exit
+ conn.close()
+ Long.MAX_VALUE
}
}
}
diff --git a/opendc-simulator/opendc-simulator-resources/src/main/kotlin/org/opendc/simulator/resources/consumer/SimConsumerBarrier.kt b/opendc-simulator/opendc-simulator-flow/src/main/kotlin/org/opendc/simulator/flow/source/FlowSourceBarrier.kt
index 52a42241..b3191ad3 100644
--- a/opendc-simulator/opendc-simulator-resources/src/main/kotlin/org/opendc/simulator/resources/consumer/SimConsumerBarrier.kt
+++ b/opendc-simulator/opendc-simulator-flow/src/main/kotlin/org/opendc/simulator/flow/source/FlowSourceBarrier.kt
@@ -20,13 +20,13 @@
* SOFTWARE.
*/
-package org.opendc.simulator.resources.consumer
+package org.opendc.simulator.flow.source
/**
- * The [SimConsumerBarrier] is a barrier that allows consumers to wait for a select number of other consumers to
- * complete, before proceeding its operation.
+ * The [FlowSourceBarrier] is a barrier that allows multiple sources to wait for a select number of other sources to
+ * finish a pull, before proceeding its operation.
*/
-public class SimConsumerBarrier(public val parties: Int) {
+public class FlowSourceBarrier(public val parties: Int) {
private var counter = 0
/**
diff --git a/opendc-simulator/opendc-simulator-flow/src/main/kotlin/org/opendc/simulator/flow/source/FlowSourceRateAdapter.kt b/opendc-simulator/opendc-simulator-flow/src/main/kotlin/org/opendc/simulator/flow/source/FlowSourceRateAdapter.kt
new file mode 100644
index 00000000..6dd60d95
--- /dev/null
+++ b/opendc-simulator/opendc-simulator-flow/src/main/kotlin/org/opendc/simulator/flow/source/FlowSourceRateAdapter.kt
@@ -0,0 +1,77 @@
+/*
+ * 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.flow.source
+
+import org.opendc.simulator.flow.FlowConnection
+import org.opendc.simulator.flow.FlowSource
+
+/**
+ * Helper class to expose an observable [rate] field describing the flow rate of the source.
+ */
+public class FlowSourceRateAdapter(
+ private val delegate: FlowSource,
+ private val callback: (Double) -> Unit = {}
+) : FlowSource by delegate {
+ /**
+ * The resource processing speed at this instant.
+ */
+ public var rate: Double = 0.0
+ private set(value) {
+ if (field != value) {
+ callback(value)
+ field = value
+ }
+ }
+
+ init {
+ callback(0.0)
+ }
+
+ override fun onStart(conn: FlowConnection, now: Long) {
+ conn.shouldSourceConverge = true
+
+ delegate.onStart(conn, now)
+ }
+
+ override fun onStop(conn: FlowConnection, now: Long, delta: Long) {
+ try {
+ delegate.onStop(conn, now, delta)
+ } finally {
+ rate = 0.0
+ }
+ }
+
+ override fun onPull(conn: FlowConnection, now: Long, delta: Long): Long {
+ return delegate.onPull(conn, now, delta)
+ }
+
+ override fun onConverge(conn: FlowConnection, now: Long, delta: Long) {
+ try {
+ delegate.onConverge(conn, now, delta)
+ } finally {
+ rate = conn.rate
+ }
+ }
+
+ override fun toString(): String = "FlowSourceRateAdapter[delegate=$delegate]"
+}
diff --git a/opendc-simulator/opendc-simulator-flow/src/main/kotlin/org/opendc/simulator/flow/source/TraceFlowSource.kt b/opendc-simulator/opendc-simulator-flow/src/main/kotlin/org/opendc/simulator/flow/source/TraceFlowSource.kt
new file mode 100644
index 00000000..ae537845
--- /dev/null
+++ b/opendc-simulator/opendc-simulator-flow/src/main/kotlin/org/opendc/simulator/flow/source/TraceFlowSource.kt
@@ -0,0 +1,67 @@
+/*
+ * 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.flow.source
+
+import org.opendc.simulator.flow.FlowConnection
+import org.opendc.simulator.flow.FlowSource
+
+/**
+ * A [FlowSource] that replays a sequence of [Fragment], each indicating the flow rate for some period of time.
+ */
+public class TraceFlowSource(private val trace: Sequence<Fragment>) : FlowSource {
+ private var _iterator: Iterator<Fragment>? = null
+ private var _nextTarget = Long.MIN_VALUE
+
+ override fun onStart(conn: FlowConnection, now: Long) {
+ check(_iterator == null) { "Source already running" }
+ _iterator = trace.iterator()
+ }
+
+ override fun onStop(conn: FlowConnection, now: Long, delta: Long) {
+ _iterator = null
+ }
+
+ override fun onPull(conn: FlowConnection, now: Long, delta: Long): Long {
+ // Check whether the trace fragment was fully consumed, otherwise wait until we have done so
+ val nextTarget = _nextTarget
+ if (nextTarget > now) {
+ return now - nextTarget
+ }
+
+ val iterator = checkNotNull(_iterator)
+ return if (iterator.hasNext()) {
+ val fragment = iterator.next()
+ _nextTarget = now + fragment.duration
+ conn.push(fragment.usage)
+ fragment.duration
+ } else {
+ conn.close()
+ Long.MAX_VALUE
+ }
+ }
+
+ /**
+ * A fragment of the trace.
+ */
+ public data class Fragment(val duration: Long, val usage: Double)
+}
diff --git a/opendc-simulator/opendc-simulator-flow/src/test/kotlin/org/opendc/simulator/flow/FlowConsumerContextTest.kt b/opendc-simulator/opendc-simulator-flow/src/test/kotlin/org/opendc/simulator/flow/FlowConsumerContextTest.kt
new file mode 100644
index 00000000..fe39eb2c
--- /dev/null
+++ b/opendc-simulator/opendc-simulator-flow/src/test/kotlin/org/opendc/simulator/flow/FlowConsumerContextTest.kt
@@ -0,0 +1,104 @@
+/*
+ * 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.flow
+
+import io.mockk.*
+import org.junit.jupiter.api.*
+import org.opendc.simulator.core.runBlockingSimulation
+import org.opendc.simulator.flow.internal.FlowConsumerContextImpl
+import org.opendc.simulator.flow.internal.FlowEngineImpl
+
+/**
+ * A test suite for the [FlowConsumerContextImpl] class.
+ */
+class FlowConsumerContextTest {
+ @Test
+ fun testFlushWithoutCommand() = runBlockingSimulation {
+ val engine = FlowEngineImpl(coroutineContext, clock)
+ val consumer = object : FlowSource {
+ override fun onPull(conn: FlowConnection, now: Long, delta: Long): Long {
+ return if (now == 0L) {
+ conn.push(1.0)
+ 1000
+ } else {
+ conn.close()
+ Long.MAX_VALUE
+ }
+ }
+ }
+
+ val logic = object : FlowConsumerLogic {}
+ val context = FlowConsumerContextImpl(engine, consumer, logic)
+
+ engine.scheduleSync(engine.clock.millis(), context)
+ }
+
+ @Test
+ fun testDoubleStart() = runBlockingSimulation {
+ val engine = FlowEngineImpl(coroutineContext, clock)
+ val consumer = object : FlowSource {
+ override fun onPull(conn: FlowConnection, now: Long, delta: Long): Long {
+ return if (now == 0L) {
+ conn.push(0.0)
+ 1000
+ } else {
+ conn.close()
+ Long.MAX_VALUE
+ }
+ }
+ }
+
+ val logic = object : FlowConsumerLogic {}
+ val context = FlowConsumerContextImpl(engine, consumer, logic)
+
+ context.start()
+
+ assertThrows<IllegalStateException> {
+ context.start()
+ }
+ }
+
+ @Test
+ fun testIdempotentCapacityChange() = runBlockingSimulation {
+ val engine = FlowEngineImpl(coroutineContext, clock)
+ val consumer = spyk(object : FlowSource {
+ override fun onPull(conn: FlowConnection, now: Long, delta: Long): Long {
+ return if (now == 0L) {
+ conn.push(1.0)
+ 1000
+ } else {
+ conn.close()
+ Long.MAX_VALUE
+ }
+ }
+ })
+
+ val logic = object : FlowConsumerLogic {}
+ val context = FlowConsumerContextImpl(engine, consumer, logic)
+ context.capacity = 4200.0
+ context.start()
+ context.capacity = 4200.0
+
+ verify(exactly = 1) { consumer.onPull(any(), any(), any()) }
+ }
+}
diff --git a/opendc-simulator/opendc-simulator-flow/src/test/kotlin/org/opendc/simulator/flow/FlowForwarderTest.kt b/opendc-simulator/opendc-simulator-flow/src/test/kotlin/org/opendc/simulator/flow/FlowForwarderTest.kt
new file mode 100644
index 00000000..12e72b8f
--- /dev/null
+++ b/opendc-simulator/opendc-simulator-flow/src/test/kotlin/org/opendc/simulator/flow/FlowForwarderTest.kt
@@ -0,0 +1,321 @@
+/*
+ * 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.flow
+
+import io.mockk.*
+import kotlinx.coroutines.*
+import org.junit.jupiter.api.Assertions.*
+import org.junit.jupiter.api.Disabled
+import org.junit.jupiter.api.Test
+import org.junit.jupiter.api.assertThrows
+import org.opendc.simulator.core.runBlockingSimulation
+import org.opendc.simulator.flow.internal.FlowEngineImpl
+import org.opendc.simulator.flow.source.FixedFlowSource
+
+/**
+ * A test suite for the [FlowForwarder] class.
+ */
+internal class FlowForwarderTest {
+ @Test
+ fun testCancelImmediately() = runBlockingSimulation {
+ val engine = FlowEngineImpl(coroutineContext, clock)
+ val forwarder = FlowForwarder(engine)
+ val source = FlowSink(engine, 2000.0)
+
+ launch { source.consume(forwarder) }
+
+ forwarder.consume(object : FlowSource {
+ override fun onPull(conn: FlowConnection, now: Long, delta: Long): Long {
+ conn.close()
+ return Long.MAX_VALUE
+ }
+ })
+
+ forwarder.close()
+ source.cancel()
+ }
+
+ @Test
+ fun testCancel() = runBlockingSimulation {
+ val engine = FlowEngineImpl(coroutineContext, clock)
+ val forwarder = FlowForwarder(engine)
+ val source = FlowSink(engine, 2000.0)
+
+ launch { source.consume(forwarder) }
+
+ forwarder.consume(object : FlowSource {
+ var isFirst = true
+
+ override fun onPull(conn: FlowConnection, now: Long, delta: Long): Long {
+ return if (isFirst) {
+ isFirst = false
+ conn.push(1.0)
+ 10 * 1000
+ } else {
+ conn.close()
+ Long.MAX_VALUE
+ }
+ }
+ })
+
+ forwarder.close()
+ source.cancel()
+ }
+
+ @Test
+ fun testState() = runBlockingSimulation {
+ val engine = FlowEngineImpl(coroutineContext, clock)
+ val forwarder = FlowForwarder(engine)
+ val consumer = object : FlowSource {
+ override fun onPull(conn: FlowConnection, now: Long, delta: Long): Long {
+ conn.close()
+ return Long.MAX_VALUE
+ }
+ }
+
+ assertFalse(forwarder.isActive)
+
+ forwarder.startConsumer(consumer)
+ assertTrue(forwarder.isActive)
+
+ assertThrows<IllegalStateException> { forwarder.startConsumer(consumer) }
+
+ forwarder.cancel()
+ assertFalse(forwarder.isActive)
+
+ forwarder.close()
+ assertFalse(forwarder.isActive)
+ }
+
+ @Test
+ fun testCancelPendingDelegate() = runBlockingSimulation {
+ val engine = FlowEngineImpl(coroutineContext, clock)
+ val forwarder = FlowForwarder(engine)
+
+ val consumer = spyk(object : FlowSource {
+ override fun onPull(conn: FlowConnection, now: Long, delta: Long): Long {
+ conn.close()
+ return Long.MAX_VALUE
+ }
+ })
+
+ forwarder.startConsumer(consumer)
+ forwarder.cancel()
+
+ verify(exactly = 0) { consumer.onStop(any(), any(), any()) }
+ }
+
+ @Test
+ fun testCancelStartedDelegate() = runBlockingSimulation {
+ val engine = FlowEngineImpl(coroutineContext, clock)
+ val forwarder = FlowForwarder(engine)
+ val source = FlowSink(engine, 2000.0)
+
+ val consumer = spyk(FixedFlowSource(2000.0, 1.0))
+
+ source.startConsumer(forwarder)
+ yield()
+ forwarder.startConsumer(consumer)
+ yield()
+ forwarder.cancel()
+
+ verify(exactly = 1) { consumer.onStart(any(), any()) }
+ verify(exactly = 1) { consumer.onStop(any(), any(), any()) }
+ }
+
+ @Test
+ fun testCancelPropagation() = runBlockingSimulation {
+ val engine = FlowEngineImpl(coroutineContext, clock)
+ val forwarder = FlowForwarder(engine)
+ val source = FlowSink(engine, 2000.0)
+
+ val consumer = spyk(FixedFlowSource(2000.0, 1.0))
+
+ source.startConsumer(forwarder)
+ yield()
+ forwarder.startConsumer(consumer)
+ yield()
+ source.cancel()
+
+ verify(exactly = 1) { consumer.onStart(any(), any()) }
+ verify(exactly = 1) { consumer.onStop(any(), any(), any()) }
+ }
+
+ @Test
+ fun testExitPropagation() = runBlockingSimulation {
+ val engine = FlowEngineImpl(coroutineContext, clock)
+ val forwarder = FlowForwarder(engine, isCoupled = true)
+ val source = FlowSink(engine, 2000.0)
+
+ val consumer = object : FlowSource {
+ override fun onPull(conn: FlowConnection, now: Long, delta: Long): Long {
+ conn.close()
+ return Long.MAX_VALUE
+ }
+ }
+
+ source.startConsumer(forwarder)
+ forwarder.consume(consumer)
+ yield()
+
+ assertFalse(forwarder.isActive)
+ }
+
+ @Test
+ @Disabled // Due to Kotlin bug: https://github.com/mockk/mockk/issues/368
+ fun testAdjustCapacity() = runBlockingSimulation {
+ val engine = FlowEngineImpl(coroutineContext, clock)
+ val forwarder = FlowForwarder(engine)
+ val sink = FlowSink(engine, 1.0)
+
+ val source = spyk(FixedFlowSource(2.0, 1.0))
+ sink.startConsumer(forwarder)
+
+ coroutineScope {
+ launch { forwarder.consume(source) }
+ delay(1000)
+ sink.capacity = 0.5
+ }
+
+ assertEquals(3000, clock.millis())
+ verify(exactly = 1) { source.onPull(any(), any(), any()) }
+ }
+
+ @Test
+ fun testCounters() = runBlockingSimulation {
+ val engine = FlowEngineImpl(coroutineContext, clock)
+ val forwarder = FlowForwarder(engine)
+ val source = FlowSink(engine, 1.0)
+
+ val consumer = FixedFlowSource(2.0, 1.0)
+ source.startConsumer(forwarder)
+
+ forwarder.consume(consumer)
+
+ yield()
+
+ assertEquals(2.0, source.counters.actual)
+ assertEquals(source.counters.actual, forwarder.counters.actual) { "Actual work" }
+ assertEquals(source.counters.demand, forwarder.counters.demand) { "Work demand" }
+ assertEquals(source.counters.remaining, forwarder.counters.remaining) { "Overcommitted work" }
+ assertEquals(2000, clock.millis())
+ }
+
+ @Test
+ fun testCoupledExit() = runBlockingSimulation {
+ val engine = FlowEngineImpl(coroutineContext, clock)
+ val forwarder = FlowForwarder(engine, isCoupled = true)
+ val source = FlowSink(engine, 2000.0)
+
+ launch { source.consume(forwarder) }
+
+ forwarder.consume(FixedFlowSource(2000.0, 1.0))
+
+ yield()
+
+ assertFalse(source.isActive)
+ }
+
+ @Test
+ fun testPullFailureCoupled() = runBlockingSimulation {
+ val engine = FlowEngineImpl(coroutineContext, clock)
+ val forwarder = FlowForwarder(engine, isCoupled = true)
+ val source = FlowSink(engine, 2000.0)
+
+ launch { source.consume(forwarder) }
+
+ try {
+ forwarder.consume(object : FlowSource {
+ override fun onPull(conn: FlowConnection, now: Long, delta: Long): Long {
+ throw IllegalStateException("Test")
+ }
+ })
+ } catch (cause: Throwable) {
+ // Ignore
+ }
+
+ yield()
+
+ assertFalse(source.isActive)
+ }
+
+ @Test
+ fun testStartFailure() = runBlockingSimulation {
+ val engine = FlowEngineImpl(coroutineContext, clock)
+ val forwarder = FlowForwarder(engine)
+ val source = FlowSink(engine, 2000.0)
+
+ launch { source.consume(forwarder) }
+
+ try {
+ forwarder.consume(object : FlowSource {
+ override fun onPull(conn: FlowConnection, now: Long, delta: Long): Long {
+ return Long.MAX_VALUE
+ }
+
+ override fun onStart(conn: FlowConnection, now: Long) {
+ throw IllegalStateException("Test")
+ }
+ })
+ } catch (cause: Throwable) {
+ // Ignore
+ }
+
+ yield()
+
+ assertTrue(source.isActive)
+ source.cancel()
+ }
+
+ @Test
+ fun testConvergeFailure() = runBlockingSimulation {
+ val engine = FlowEngineImpl(coroutineContext, clock)
+ val forwarder = FlowForwarder(engine)
+ val source = FlowSink(engine, 2000.0)
+
+ launch { source.consume(forwarder) }
+
+ try {
+ forwarder.consume(object : FlowSource {
+ override fun onStart(conn: FlowConnection, now: Long) {
+ conn.shouldSourceConverge = true
+ }
+
+ override fun onPull(conn: FlowConnection, now: Long, delta: Long): Long {
+ return Long.MAX_VALUE
+ }
+
+ override fun onConverge(conn: FlowConnection, now: Long, delta: Long) {
+ throw IllegalStateException("Test")
+ }
+ })
+ } catch (cause: Throwable) {
+ // Ignore
+ }
+
+ yield()
+
+ assertTrue(source.isActive)
+ source.cancel()
+ }
+}
diff --git a/opendc-simulator/opendc-simulator-flow/src/test/kotlin/org/opendc/simulator/flow/FlowSinkTest.kt b/opendc-simulator/opendc-simulator-flow/src/test/kotlin/org/opendc/simulator/flow/FlowSinkTest.kt
new file mode 100644
index 00000000..70c75864
--- /dev/null
+++ b/opendc-simulator/opendc-simulator-flow/src/test/kotlin/org/opendc/simulator/flow/FlowSinkTest.kt
@@ -0,0 +1,241 @@
+/*
+ * 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.flow
+
+import io.mockk.spyk
+import io.mockk.verify
+import kotlinx.coroutines.*
+import org.junit.jupiter.api.*
+import org.junit.jupiter.api.Assertions.assertEquals
+import org.opendc.simulator.core.runBlockingSimulation
+import org.opendc.simulator.flow.internal.FlowEngineImpl
+import org.opendc.simulator.flow.source.FixedFlowSource
+import org.opendc.simulator.flow.source.FlowSourceRateAdapter
+
+/**
+ * A test suite for the [FlowSink] class.
+ */
+internal class FlowSinkTest {
+ @Test
+ fun testSpeed() = runBlockingSimulation {
+ val engine = FlowEngineImpl(coroutineContext, clock)
+ val capacity = 4200.0
+ val provider = FlowSink(engine, capacity)
+
+ val consumer = FixedFlowSource(4200.0, 1.0)
+
+ val res = mutableListOf<Double>()
+ val adapter = FlowSourceRateAdapter(consumer, res::add)
+
+ provider.consume(adapter)
+
+ assertEquals(listOf(0.0, capacity, 0.0), res) { "Speed is reported correctly" }
+ }
+
+ @Test
+ fun testAdjustCapacity() = runBlockingSimulation {
+ val engine = FlowEngineImpl(coroutineContext, clock)
+ val provider = FlowSink(engine, 1.0)
+
+ val consumer = spyk(FixedFlowSource(2.0, 1.0))
+
+ coroutineScope {
+ launch { provider.consume(consumer) }
+ delay(1000)
+ provider.capacity = 0.5
+ }
+ assertEquals(3000, clock.millis())
+ verify(exactly = 3) { consumer.onPull(any(), any(), any()) }
+ }
+
+ @Test
+ fun testSpeedLimit() = runBlockingSimulation {
+ val engine = FlowEngineImpl(coroutineContext, clock)
+ val capacity = 4200.0
+ val provider = FlowSink(engine, capacity)
+
+ val consumer = FixedFlowSource(capacity, 2.0)
+
+ val res = mutableListOf<Double>()
+ val adapter = FlowSourceRateAdapter(consumer, res::add)
+
+ provider.consume(adapter)
+
+ assertEquals(listOf(0.0, capacity, 0.0), res) { "Speed is reported correctly" }
+ }
+
+ /**
+ * Test to see whether no infinite recursion occurs when interrupting during [FlowSource.onStart] or
+ * [FlowSource.onPull].
+ */
+ @Test
+ fun testIntermediateInterrupt() = runBlockingSimulation {
+ val engine = FlowEngineImpl(coroutineContext, clock)
+ val capacity = 4200.0
+ val provider = FlowSink(engine, capacity)
+
+ val consumer = object : FlowSource {
+ override fun onPull(conn: FlowConnection, now: Long, delta: Long): Long {
+ conn.close()
+ return Long.MAX_VALUE
+ }
+
+ override fun onStart(conn: FlowConnection, now: Long) {
+ conn.pull()
+ }
+ }
+
+ provider.consume(consumer)
+ }
+
+ @Test
+ fun testInterrupt() = runBlockingSimulation {
+ val engine = FlowEngineImpl(coroutineContext, clock)
+ val capacity = 4200.0
+ val provider = FlowSink(engine, capacity)
+ lateinit var resCtx: FlowConnection
+
+ val consumer = object : FlowSource {
+ var isFirst = true
+
+ override fun onStart(conn: FlowConnection, now: Long) {
+ resCtx = conn
+ }
+
+ override fun onPull(conn: FlowConnection, now: Long, delta: Long): Long {
+ return if (isFirst) {
+ isFirst = false
+ conn.push(1.0)
+ 4000
+ } else {
+ conn.close()
+ Long.MAX_VALUE
+ }
+ }
+ }
+
+ launch {
+ yield()
+ resCtx.pull()
+ }
+ provider.consume(consumer)
+
+ assertEquals(0, clock.millis())
+ }
+
+ @Test
+ fun testFailure() = runBlockingSimulation {
+ val engine = FlowEngineImpl(coroutineContext, clock)
+ val capacity = 4200.0
+ val provider = FlowSink(engine, capacity)
+
+ val consumer = object : FlowSource {
+ override fun onStart(conn: FlowConnection, now: Long) {
+ throw IllegalStateException("Hi")
+ }
+
+ override fun onPull(conn: FlowConnection, now: Long, delta: Long): Long {
+ return Long.MAX_VALUE
+ }
+ }
+
+ assertThrows<IllegalStateException> {
+ provider.consume(consumer)
+ }
+ }
+
+ @Test
+ fun testExceptionPropagationOnNext() = runBlockingSimulation {
+ val engine = FlowEngineImpl(coroutineContext, clock)
+ val capacity = 4200.0
+ val provider = FlowSink(engine, capacity)
+
+ val consumer = object : FlowSource {
+ var isFirst = true
+
+ override fun onPull(conn: FlowConnection, now: Long, delta: Long): Long {
+ return if (isFirst) {
+ isFirst = false
+ conn.push(1.0)
+ 1000
+ } else {
+ throw IllegalStateException()
+ }
+ }
+ }
+
+ assertThrows<IllegalStateException> {
+ provider.consume(consumer)
+ }
+ }
+
+ @Test
+ fun testConcurrentConsumption() = runBlockingSimulation {
+ val engine = FlowEngineImpl(coroutineContext, clock)
+ val capacity = 4200.0
+ val provider = FlowSink(engine, capacity)
+
+ val consumer = FixedFlowSource(capacity, 1.0)
+
+ assertThrows<IllegalStateException> {
+ coroutineScope {
+ launch { provider.consume(consumer) }
+ provider.consume(consumer)
+ }
+ }
+ }
+
+ @Test
+ fun testCancelDuringConsumption() = runBlockingSimulation {
+ val engine = FlowEngineImpl(coroutineContext, clock)
+ val capacity = 4200.0
+ val provider = FlowSink(engine, capacity)
+
+ val consumer = FixedFlowSource(capacity, 1.0)
+
+ launch { provider.consume(consumer) }
+ delay(500)
+ provider.cancel()
+
+ yield()
+
+ assertEquals(500, clock.millis())
+ }
+
+ @Test
+ fun testInfiniteSleep() {
+ assertThrows<IllegalStateException> {
+ runBlockingSimulation {
+ val engine = FlowEngineImpl(coroutineContext, clock)
+ val capacity = 4200.0
+ val provider = FlowSink(engine, capacity)
+
+ val consumer = object : FlowSource {
+ override fun onPull(conn: FlowConnection, now: Long, delta: Long): Long = Long.MAX_VALUE
+ }
+
+ provider.consume(consumer)
+ }
+ }
+ }
+}
diff --git a/opendc-simulator/opendc-simulator-flow/src/test/kotlin/org/opendc/simulator/flow/mux/ForwardingFlowMultiplexerTest.kt b/opendc-simulator/opendc-simulator-flow/src/test/kotlin/org/opendc/simulator/flow/mux/ForwardingFlowMultiplexerTest.kt
new file mode 100644
index 00000000..187dacd9
--- /dev/null
+++ b/opendc-simulator/opendc-simulator-flow/src/test/kotlin/org/opendc/simulator/flow/mux/ForwardingFlowMultiplexerTest.kt
@@ -0,0 +1,154 @@
+/*
+ * 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.flow.mux
+
+import kotlinx.coroutines.yield
+import org.junit.jupiter.api.Assertions.assertEquals
+import org.junit.jupiter.api.Test
+import org.junit.jupiter.api.assertAll
+import org.junit.jupiter.api.assertThrows
+import org.opendc.simulator.core.runBlockingSimulation
+import org.opendc.simulator.flow.*
+import org.opendc.simulator.flow.internal.FlowEngineImpl
+import org.opendc.simulator.flow.source.FixedFlowSource
+import org.opendc.simulator.flow.source.FlowSourceRateAdapter
+import org.opendc.simulator.flow.source.TraceFlowSource
+
+/**
+ * Test suite for the [ForwardingFlowMultiplexer] class.
+ */
+internal class ForwardingFlowMultiplexerTest {
+ /**
+ * Test a trace workload.
+ */
+ @Test
+ fun testTrace() = runBlockingSimulation {
+ val engine = FlowEngineImpl(coroutineContext, clock)
+
+ val speed = mutableListOf<Double>()
+
+ val duration = 5 * 60L
+ val workload =
+ TraceFlowSource(
+ sequenceOf(
+ TraceFlowSource.Fragment(duration * 1000, 28.0),
+ TraceFlowSource.Fragment(duration * 1000, 3500.0),
+ TraceFlowSource.Fragment(duration * 1000, 0.0),
+ TraceFlowSource.Fragment(duration * 1000, 183.0)
+ ),
+ )
+
+ val switch = ForwardingFlowMultiplexer(engine)
+ val source = FlowSink(engine, 3200.0)
+ val forwarder = FlowForwarder(engine)
+ val adapter = FlowSourceRateAdapter(forwarder, speed::add)
+ source.startConsumer(adapter)
+ forwarder.startConsumer(switch.newOutput())
+
+ val provider = switch.newInput()
+ provider.consume(workload)
+ yield()
+
+ assertAll(
+ { assertEquals(listOf(0.0, 28.0, 3200.0, 0.0, 183.0, 0.0), speed) { "Correct speed" } },
+ { assertEquals(5 * 60L * 4000, clock.millis()) { "Took enough time" } }
+ )
+ }
+
+ /**
+ * Test runtime workload on hypervisor.
+ */
+ @Test
+ fun testRuntimeWorkload() = runBlockingSimulation {
+ val engine = FlowEngineImpl(coroutineContext, clock)
+
+ val duration = 5 * 60L * 1000
+ val workload = FixedFlowSource(duration * 3.2, 1.0)
+
+ val switch = ForwardingFlowMultiplexer(engine)
+ val source = FlowSink(engine, 3200.0)
+
+ source.startConsumer(switch.newOutput())
+
+ val provider = switch.newInput()
+ provider.consume(workload)
+ yield()
+
+ assertEquals(duration, clock.millis()) { "Took enough time" }
+ }
+
+ /**
+ * Test two workloads running sequentially.
+ */
+ @Test
+ fun testTwoWorkloads() = runBlockingSimulation {
+ val engine = FlowEngineImpl(coroutineContext, clock)
+
+ val duration = 5 * 60L * 1000
+ val workload = object : FlowSource {
+ var isFirst = true
+
+ override fun onStart(conn: FlowConnection, now: Long) {
+ isFirst = true
+ }
+
+ override fun onPull(conn: FlowConnection, now: Long, delta: Long): Long {
+ return if (isFirst) {
+ isFirst = false
+ conn.push(1.0)
+ duration
+ } else {
+ conn.close()
+ Long.MAX_VALUE
+ }
+ }
+ }
+
+ val switch = ForwardingFlowMultiplexer(engine)
+ val source = FlowSink(engine, 3200.0)
+
+ source.startConsumer(switch.newOutput())
+
+ val provider = switch.newInput()
+ provider.consume(workload)
+ yield()
+ provider.consume(workload)
+ assertEquals(duration * 2, clock.millis()) { "Took enough time" }
+ }
+
+ /**
+ * Test concurrent workloads on the machine.
+ */
+ @Test
+ fun testConcurrentWorkloadFails() = runBlockingSimulation {
+ val engine = FlowEngineImpl(coroutineContext, clock)
+
+ val switch = ForwardingFlowMultiplexer(engine)
+ val source = FlowSink(engine, 3200.0)
+
+ source.startConsumer(switch.newOutput())
+
+ switch.newInput()
+ assertThrows<IllegalStateException> { switch.newInput() }
+ }
+}
diff --git a/opendc-simulator/opendc-simulator-flow/src/test/kotlin/org/opendc/simulator/flow/mux/MaxMinFlowMultiplexerTest.kt b/opendc-simulator/opendc-simulator-flow/src/test/kotlin/org/opendc/simulator/flow/mux/MaxMinFlowMultiplexerTest.kt
new file mode 100644
index 00000000..6e2cdb98
--- /dev/null
+++ b/opendc-simulator/opendc-simulator-flow/src/test/kotlin/org/opendc/simulator/flow/mux/MaxMinFlowMultiplexerTest.kt
@@ -0,0 +1,149 @@
+/*
+ * 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.flow.mux
+
+import kotlinx.coroutines.coroutineScope
+import kotlinx.coroutines.launch
+import kotlinx.coroutines.yield
+import org.junit.jupiter.api.*
+import org.junit.jupiter.api.Assertions.assertEquals
+import org.opendc.simulator.core.runBlockingSimulation
+import org.opendc.simulator.flow.FlowSink
+import org.opendc.simulator.flow.consume
+import org.opendc.simulator.flow.internal.FlowEngineImpl
+import org.opendc.simulator.flow.source.FixedFlowSource
+import org.opendc.simulator.flow.source.TraceFlowSource
+
+/**
+ * Test suite for the [FlowMultiplexer] implementations
+ */
+internal class MaxMinFlowMultiplexerTest {
+ @Test
+ fun testSmoke() = runBlockingSimulation {
+ val scheduler = FlowEngineImpl(coroutineContext, clock)
+ val switch = MaxMinFlowMultiplexer(scheduler)
+
+ val sources = List(2) { FlowSink(scheduler, 2000.0) }
+ sources.forEach { it.startConsumer(switch.newOutput()) }
+
+ val provider = switch.newInput()
+ val consumer = FixedFlowSource(2000.0, 1.0)
+
+ try {
+ provider.consume(consumer)
+ yield()
+ } finally {
+ switch.clear()
+ }
+ }
+
+ /**
+ * Test overcommitting of resources via the hypervisor with a single VM.
+ */
+ @Test
+ fun testOvercommittedSingle() = runBlockingSimulation {
+ val scheduler = FlowEngineImpl(coroutineContext, clock)
+
+ val duration = 5 * 60L
+ val workload =
+ TraceFlowSource(
+ sequenceOf(
+ TraceFlowSource.Fragment(duration * 1000, 28.0),
+ TraceFlowSource.Fragment(duration * 1000, 3500.0),
+ TraceFlowSource.Fragment(duration * 1000, 0.0),
+ TraceFlowSource.Fragment(duration * 1000, 183.0)
+ ),
+ )
+
+ val switch = MaxMinFlowMultiplexer(scheduler)
+ val sink = FlowSink(scheduler, 3200.0)
+ val provider = switch.newInput()
+
+ try {
+ sink.startConsumer(switch.newOutput())
+ provider.consume(workload)
+ yield()
+ } finally {
+ switch.clear()
+ }
+
+ assertAll(
+ { assertEquals(1113300.0, switch.counters.demand, "Requested work does not match") },
+ { assertEquals(1023300.0, switch.counters.actual, "Actual work does not match") },
+ { assertEquals(2816700.0, switch.counters.remaining, "Remaining capacity does not match") },
+ { assertEquals(1200000, clock.millis()) }
+ )
+ }
+
+ /**
+ * Test overcommitting of resources via the hypervisor with two VMs.
+ */
+ @Test
+ fun testOvercommittedDual() = runBlockingSimulation {
+ val scheduler = FlowEngineImpl(coroutineContext, clock)
+
+ val duration = 5 * 60L
+ val workloadA =
+ TraceFlowSource(
+ sequenceOf(
+ TraceFlowSource.Fragment(duration * 1000, 28.0),
+ TraceFlowSource.Fragment(duration * 1000, 3500.0),
+ TraceFlowSource.Fragment(duration * 1000, 0.0),
+ TraceFlowSource.Fragment(duration * 1000, 183.0)
+ ),
+ )
+ val workloadB =
+ TraceFlowSource(
+ sequenceOf(
+ TraceFlowSource.Fragment(duration * 1000, 28.0),
+ TraceFlowSource.Fragment(duration * 1000, 3100.0),
+ TraceFlowSource.Fragment(duration * 1000, 0.0),
+ TraceFlowSource.Fragment(duration * 1000, 73.0)
+ )
+ )
+
+ val switch = MaxMinFlowMultiplexer(scheduler)
+ val sink = FlowSink(scheduler, 3200.0)
+ val providerA = switch.newInput()
+ val providerB = switch.newInput()
+
+ try {
+ sink.startConsumer(switch.newOutput())
+
+ coroutineScope {
+ launch { providerA.consume(workloadA) }
+ providerB.consume(workloadB)
+ }
+
+ yield()
+ } finally {
+ switch.clear()
+ }
+ assertAll(
+ { assertEquals(2073600.0, switch.counters.demand, "Requested work does not match") },
+ { assertEquals(1053600.0, switch.counters.actual, "Granted work does not match") },
+ { assertEquals(2786400.0, switch.counters.remaining, "Remaining capacity does not match") },
+ { assertEquals(1200000, clock.millis()) }
+ )
+ }
+}
diff --git a/opendc-simulator/opendc-simulator-resources/src/test/kotlin/org/opendc/simulator/resources/SimWorkConsumerTest.kt b/opendc-simulator/opendc-simulator-flow/src/test/kotlin/org/opendc/simulator/flow/source/FixedFlowSourceTest.kt
index ac8b5814..8396d346 100644
--- a/opendc-simulator/opendc-simulator-resources/src/test/kotlin/org/opendc/simulator/resources/SimWorkConsumerTest.kt
+++ b/opendc-simulator/opendc-simulator-flow/src/test/kotlin/org/opendc/simulator/flow/source/FixedFlowSourceTest.kt
@@ -20,46 +20,38 @@
* SOFTWARE.
*/
-package org.opendc.simulator.resources
+package org.opendc.simulator.flow.source
-import kotlinx.coroutines.ExperimentalCoroutinesApi
import org.junit.jupiter.api.Assertions.assertEquals
import org.junit.jupiter.api.Test
import org.opendc.simulator.core.runBlockingSimulation
-import org.opendc.simulator.resources.consumer.SimWorkConsumer
+import org.opendc.simulator.flow.FlowSink
+import org.opendc.simulator.flow.consume
+import org.opendc.simulator.flow.internal.FlowEngineImpl
/**
- * A test suite for the [SimWorkConsumer] class.
+ * A test suite for the [FixedFlowSource] class.
*/
-@OptIn(ExperimentalCoroutinesApi::class)
-internal class SimWorkConsumerTest {
+internal class FixedFlowSourceTest {
@Test
fun testSmoke() = runBlockingSimulation {
- val scheduler = SimResourceSchedulerTrampoline(coroutineContext, clock)
- val provider = SimResourceSource(1.0, scheduler)
+ val scheduler = FlowEngineImpl(coroutineContext, clock)
+ val provider = FlowSink(scheduler, 1.0)
- val consumer = SimWorkConsumer(1.0, 1.0)
+ val consumer = FixedFlowSource(1.0, 1.0)
- try {
- provider.consume(consumer)
- assertEquals(1000, clock.millis())
- } finally {
- provider.close()
- }
+ provider.consume(consumer)
+ assertEquals(1000, clock.millis())
}
@Test
fun testUtilization() = runBlockingSimulation {
- val scheduler = SimResourceSchedulerTrampoline(coroutineContext, clock)
- val provider = SimResourceSource(1.0, scheduler)
+ val scheduler = FlowEngineImpl(coroutineContext, clock)
+ val provider = FlowSink(scheduler, 1.0)
- val consumer = SimWorkConsumer(1.0, 0.5)
+ val consumer = FixedFlowSource(1.0, 0.5)
- try {
- provider.consume(consumer)
- assertEquals(2000, clock.millis())
- } finally {
- provider.close()
- }
+ provider.consume(consumer)
+ assertEquals(2000, clock.millis())
}
}
diff --git a/opendc-simulator/opendc-simulator-network/build.gradle.kts b/opendc-simulator/opendc-simulator-network/build.gradle.kts
new file mode 100644
index 00000000..f8931053
--- /dev/null
+++ b/opendc-simulator/opendc-simulator-network/build.gradle.kts
@@ -0,0 +1,37 @@
+/*
+ * 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.
+ */
+
+description = "Library for simulating datacenter network components"
+
+plugins {
+ `kotlin-library-conventions`
+ `testing-conventions`
+ `jacoco-conventions`
+}
+
+dependencies {
+ api(platform(projects.opendcPlatform))
+ api(projects.opendcSimulator.opendcSimulatorFlow)
+ implementation(projects.opendcSimulator.opendcSimulatorCore)
+
+ testImplementation(libs.slf4j.simple)
+}
diff --git a/opendc-simulator/opendc-simulator-network/src/main/kotlin/org/opendc/simulator/network/SimNetworkLink.kt b/opendc-simulator/opendc-simulator-network/src/main/kotlin/org/opendc/simulator/network/SimNetworkLink.kt
new file mode 100644
index 00000000..67562640
--- /dev/null
+++ b/opendc-simulator/opendc-simulator-network/src/main/kotlin/org/opendc/simulator/network/SimNetworkLink.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.network
+
+/**
+ * A physical bi-directional communication link between two [SimNetworkPort]s.
+ *
+ * @param left The first port of the link.
+ * @param right The second port of the link.
+ */
+public class SimNetworkLink(public val left: SimNetworkPort, public val right: SimNetworkPort) {
+ /**
+ * Determine whether the specified [port] participates in this network link.
+ */
+ public operator fun contains(port: SimNetworkPort): Boolean = port == left || port == right
+
+ /**
+ * Obtain the opposite port to which the specified [port] is connected through this link.
+ */
+ public fun opposite(port: SimNetworkPort): SimNetworkPort {
+ return when (port) {
+ left -> right
+ right -> left
+ else -> throw IllegalArgumentException("Invalid port given")
+ }
+ }
+
+ override fun toString(): String = "SimNetworkLink[left=$left,right=$right]"
+}
diff --git a/opendc-simulator/opendc-simulator-network/src/main/kotlin/org/opendc/simulator/network/SimNetworkPort.kt b/opendc-simulator/opendc-simulator-network/src/main/kotlin/org/opendc/simulator/network/SimNetworkPort.kt
new file mode 100644
index 00000000..4b66d5cf
--- /dev/null
+++ b/opendc-simulator/opendc-simulator-network/src/main/kotlin/org/opendc/simulator/network/SimNetworkPort.kt
@@ -0,0 +1,91 @@
+/*
+ * 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.network
+
+import org.opendc.simulator.flow.FlowConsumer
+import org.opendc.simulator.flow.FlowSource
+
+/**
+ * A network port allows network devices to be connected to network through links.
+ */
+public abstract class SimNetworkPort {
+ /**
+ * A flag to indicate that the network port is connected to another port.
+ */
+ public val isConnected: Boolean
+ get() = _link != null
+
+ /**
+ * The network link which connects this port to another port.
+ */
+ public val link: SimNetworkLink?
+ get() = _link
+ private var _link: SimNetworkLink? = null
+
+ /**
+ * Connect this port to the specified [port].
+ */
+ public fun connect(port: SimNetworkPort) {
+ require(port !== this) { "Circular reference" }
+ check(!isConnected) { "Port already connected" }
+ check(!port.isConnected) { "Target port already connected" }
+
+ val link = SimNetworkLink(this, port)
+ _link = link
+ port._link = link
+
+ // Start bi-directional flow channel between the two ports
+ try {
+ provider.startConsumer(port.createConsumer())
+ port.provider.startConsumer(createConsumer())
+ } catch (e: Throwable) {
+ disconnect()
+ throw e
+ }
+ }
+
+ /**
+ * Disconnect the current network link if it exists.
+ */
+ public fun disconnect() {
+ val link = _link ?: return
+ val opposite = link.opposite(this)
+ _link = null
+ opposite._link = null
+
+ provider.cancel()
+ opposite.provider.cancel()
+ }
+
+ /**
+ * Create a [FlowSource] which generates the outgoing traffic of this port.
+ */
+ protected abstract fun createConsumer(): FlowSource
+
+ /**
+ * The [FlowConsumer] which processes the ingoing traffic of this port.
+ */
+ protected abstract val provider: FlowConsumer
+
+ override fun toString(): String = "SimNetworkPort[isConnected=$isConnected]"
+}
diff --git a/opendc-simulator/opendc-simulator-network/src/main/kotlin/org/opendc/simulator/network/SimNetworkSink.kt b/opendc-simulator/opendc-simulator-network/src/main/kotlin/org/opendc/simulator/network/SimNetworkSink.kt
new file mode 100644
index 00000000..4b0d7bbd
--- /dev/null
+++ b/opendc-simulator/opendc-simulator-network/src/main/kotlin/org/opendc/simulator/network/SimNetworkSink.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.network
+
+import org.opendc.simulator.flow.*
+
+/**
+ * A network sink which discards all received traffic and does not generate any traffic itself.
+ */
+public class SimNetworkSink(
+ engine: FlowEngine,
+ public val capacity: Double
+) : SimNetworkPort() {
+ override fun createConsumer(): FlowSource = object : FlowSource {
+ override fun onPull(conn: FlowConnection, now: Long, delta: Long): Long = Long.MAX_VALUE
+
+ override fun toString(): String = "SimNetworkSink.Consumer"
+ }
+
+ override val provider: FlowConsumer = FlowSink(engine, capacity)
+
+ override fun toString(): String = "SimNetworkSink[capacity=$capacity]"
+}
diff --git a/opendc-simulator/opendc-simulator-failures/src/main/kotlin/org/opendc/simulator/failures/FaultInjector.kt b/opendc-simulator/opendc-simulator-network/src/main/kotlin/org/opendc/simulator/network/SimNetworkSwitch.kt
index a866260c..7dc249ab 100644
--- a/opendc-simulator/opendc-simulator-failures/src/main/kotlin/org/opendc/simulator/failures/FaultInjector.kt
+++ b/opendc-simulator/opendc-simulator-network/src/main/kotlin/org/opendc/simulator/network/SimNetworkSwitch.kt
@@ -1,5 +1,5 @@
/*
- * Copyright (c) 2020 AtLarge Research
+ * 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
@@ -20,14 +20,14 @@
* SOFTWARE.
*/
-package org.opendc.simulator.failures
+package org.opendc.simulator.network
/**
- * An interface for stochastically injecting faults into a running system.
+ * A network device connects devices on a network by switching the traffic over its ports.
*/
-public interface FaultInjector {
+public interface SimNetworkSwitch {
/**
- * Enqueue the specified [FailureDomain] into the queue as candidate for failure injection in the future.
+ * The ports of the switch.
*/
- public fun enqueue(domain: FailureDomain)
+ public val ports: List<SimNetworkPort>
}
diff --git a/opendc-simulator/opendc-simulator-network/src/main/kotlin/org/opendc/simulator/network/SimNetworkSwitchVirtual.kt b/opendc-simulator/opendc-simulator-network/src/main/kotlin/org/opendc/simulator/network/SimNetworkSwitchVirtual.kt
new file mode 100644
index 00000000..6667c80c
--- /dev/null
+++ b/opendc-simulator/opendc-simulator-network/src/main/kotlin/org/opendc/simulator/network/SimNetworkSwitchVirtual.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.network
+
+import org.opendc.simulator.flow.*
+import org.opendc.simulator.flow.mux.MaxMinFlowMultiplexer
+
+/**
+ * A [SimNetworkSwitch] that can support new networking ports on demand.
+ */
+public class SimNetworkSwitchVirtual(private val engine: FlowEngine) : SimNetworkSwitch {
+ /**
+ * The ports of this switch.
+ */
+ override val ports: List<Port>
+ get() = _ports
+ private val _ports = mutableListOf<Port>()
+
+ /**
+ * The [MaxMinFlowMultiplexer] to actually perform the switching.
+ */
+ private val mux = MaxMinFlowMultiplexer(engine)
+
+ /**
+ * Open a new port on the switch.
+ */
+ public fun newPort(): Port {
+ val port = Port()
+ _ports.add(port)
+ return port
+ }
+
+ /**
+ * A port on the network switch.
+ */
+ public inner class Port : SimNetworkPort(), AutoCloseable {
+ /**
+ * A flag to indicate that this virtual port was removed from the switch.
+ */
+ private var isClosed: Boolean = false
+
+ override val provider: FlowConsumer
+ get() = _provider
+ private val _provider = mux.newInput()
+
+ private val _source = mux.newOutput()
+
+ override fun createConsumer(): FlowSource = _source
+
+ override fun close() {
+ isClosed = true
+ mux.removeInput(_provider)
+ _ports.remove(this)
+ }
+ }
+}
diff --git a/opendc-simulator/opendc-simulator-network/src/test/kotlin/org/opendc/simulator/network/SimNetworkLinkTest.kt b/opendc-simulator/opendc-simulator-network/src/test/kotlin/org/opendc/simulator/network/SimNetworkLinkTest.kt
new file mode 100644
index 00000000..3480c9df
--- /dev/null
+++ b/opendc-simulator/opendc-simulator-network/src/test/kotlin/org/opendc/simulator/network/SimNetworkLinkTest.kt
@@ -0,0 +1,89 @@
+/*
+ * 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.network
+
+import io.mockk.mockk
+import org.junit.jupiter.api.Assertions.*
+import org.junit.jupiter.api.Test
+import org.junit.jupiter.api.assertThrows
+
+/**
+ * Test suite for [SimNetworkLink] class.
+ */
+class SimNetworkLinkTest {
+ @Test
+ fun testContainsLeft() {
+ val left = mockk<SimNetworkPort>()
+ val right = mockk<SimNetworkPort>()
+
+ val link = SimNetworkLink(left, right)
+ assertTrue(left in link)
+ }
+
+ @Test
+ fun testContainsRight() {
+ val left = mockk<SimNetworkPort>()
+ val right = mockk<SimNetworkPort>()
+
+ val link = SimNetworkLink(left, right)
+ assertTrue(right in link)
+ }
+
+ @Test
+ fun testContainsNone() {
+ val left = mockk<SimNetworkPort>()
+ val right = mockk<SimNetworkPort>()
+ val none = mockk<SimNetworkPort>()
+
+ val link = SimNetworkLink(left, right)
+ assertFalse(none in link)
+ }
+
+ @Test
+ fun testOppositeLeft() {
+ val left = mockk<SimNetworkPort>()
+ val right = mockk<SimNetworkPort>()
+
+ val link = SimNetworkLink(left, right)
+ assertEquals(right, link.opposite(left))
+ }
+
+ @Test
+ fun testOppositeRight() {
+ val left = mockk<SimNetworkPort>()
+ val right = mockk<SimNetworkPort>()
+
+ val link = SimNetworkLink(left, right)
+ assertEquals(left, link.opposite(right))
+ }
+
+ @Test
+ fun testOppositeNone() {
+ val left = mockk<SimNetworkPort>()
+ val right = mockk<SimNetworkPort>()
+ val none = mockk<SimNetworkPort>()
+
+ val link = SimNetworkLink(left, right)
+ assertThrows<IllegalArgumentException> { link.opposite(none) }
+ }
+}
diff --git a/opendc-simulator/opendc-simulator-network/src/test/kotlin/org/opendc/simulator/network/SimNetworkSinkTest.kt b/opendc-simulator/opendc-simulator-network/src/test/kotlin/org/opendc/simulator/network/SimNetworkSinkTest.kt
new file mode 100644
index 00000000..14d22162
--- /dev/null
+++ b/opendc-simulator/opendc-simulator-network/src/test/kotlin/org/opendc/simulator/network/SimNetworkSinkTest.kt
@@ -0,0 +1,137 @@
+/*
+ * 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.network
+
+import io.mockk.every
+import io.mockk.mockk
+import io.mockk.spyk
+import io.mockk.verify
+import org.junit.jupiter.api.Assertions.*
+import org.junit.jupiter.api.Test
+import org.junit.jupiter.api.assertDoesNotThrow
+import org.junit.jupiter.api.assertThrows
+import org.opendc.simulator.core.runBlockingSimulation
+import org.opendc.simulator.flow.*
+import org.opendc.simulator.flow.source.FixedFlowSource
+
+/**
+ * Test suite for the [SimNetworkSink] class.
+ */
+class SimNetworkSinkTest {
+ @Test
+ fun testInitialState() = runBlockingSimulation {
+ val engine = FlowEngine(coroutineContext, clock)
+ val sink = SimNetworkSink(engine, capacity = 100.0)
+
+ assertFalse(sink.isConnected)
+ assertNull(sink.link)
+ assertEquals(100.0, sink.capacity)
+ }
+
+ @Test
+ fun testDisconnectIdempotent() = runBlockingSimulation {
+ val engine = FlowEngine(coroutineContext, clock)
+ val sink = SimNetworkSink(engine, capacity = 100.0)
+
+ assertDoesNotThrow { sink.disconnect() }
+ assertFalse(sink.isConnected)
+ }
+
+ @Test
+ fun testConnectCircular() = runBlockingSimulation {
+ val engine = FlowEngine(coroutineContext, clock)
+ val sink = SimNetworkSink(engine, capacity = 100.0)
+
+ assertThrows<IllegalArgumentException> {
+ sink.connect(sink)
+ }
+ }
+
+ @Test
+ fun testConnectAlreadyConnectedTarget() = runBlockingSimulation {
+ val engine = FlowEngine(coroutineContext, clock)
+ val sink = SimNetworkSink(engine, capacity = 100.0)
+ val source = mockk<SimNetworkPort>(relaxUnitFun = true)
+ every { source.isConnected } returns true
+
+ assertThrows<IllegalStateException> {
+ sink.connect(source)
+ }
+ }
+
+ @Test
+ fun testConnectAlreadyConnected() = runBlockingSimulation {
+ val engine = FlowEngine(coroutineContext, clock)
+ val sink = SimNetworkSink(engine, capacity = 100.0)
+ val source1 = Source(engine)
+
+ val source2 = mockk<SimNetworkPort>(relaxUnitFun = true)
+
+ every { source2.isConnected } returns false
+
+ sink.connect(source1)
+ assertThrows<IllegalStateException> {
+ sink.connect(source2)
+ }
+ }
+
+ @Test
+ fun testConnect() = runBlockingSimulation {
+ val engine = FlowEngine(coroutineContext, clock)
+ val sink = SimNetworkSink(engine, capacity = 100.0)
+ val source = spyk(Source(engine))
+ val consumer = source.consumer
+
+ sink.connect(source)
+
+ assertTrue(sink.isConnected)
+ assertTrue(source.isConnected)
+
+ verify { source.createConsumer() }
+ verify { consumer.onStart(any(), any()) }
+ }
+
+ @Test
+ fun testDisconnect() = runBlockingSimulation {
+ val engine = FlowEngine(coroutineContext, clock)
+ val sink = SimNetworkSink(engine, capacity = 100.0)
+ val source = spyk(Source(engine))
+ val consumer = source.consumer
+
+ sink.connect(source)
+ sink.disconnect()
+
+ assertFalse(sink.isConnected)
+ assertFalse(source.isConnected)
+
+ verify { consumer.onStop(any(), any(), any()) }
+ }
+
+ private class Source(engine: FlowEngine) : SimNetworkPort() {
+ val consumer = spyk(FixedFlowSource(Double.POSITIVE_INFINITY, utilization = 0.8))
+
+ public override fun createConsumer(): FlowSource = consumer
+
+ override val provider: FlowConsumer = FlowSink(engine, 0.0)
+ }
+}
diff --git a/opendc-simulator/opendc-simulator-network/src/test/kotlin/org/opendc/simulator/network/SimNetworkSwitchVirtualTest.kt b/opendc-simulator/opendc-simulator-network/src/test/kotlin/org/opendc/simulator/network/SimNetworkSwitchVirtualTest.kt
new file mode 100644
index 00000000..62e54ffb
--- /dev/null
+++ b/opendc-simulator/opendc-simulator-network/src/test/kotlin/org/opendc/simulator/network/SimNetworkSwitchVirtualTest.kt
@@ -0,0 +1,77 @@
+/*
+ * 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.network
+
+import io.mockk.spyk
+import io.mockk.verify
+import org.junit.jupiter.api.Assertions.assertTrue
+import org.junit.jupiter.api.Test
+import org.junit.jupiter.api.assertThrows
+import org.opendc.simulator.core.runBlockingSimulation
+import org.opendc.simulator.flow.*
+import org.opendc.simulator.flow.source.FixedFlowSource
+
+/**
+ * Test suite for the [SimNetworkSwitchVirtual] class.
+ */
+class SimNetworkSwitchVirtualTest {
+ @Test
+ fun testConnect() = runBlockingSimulation {
+ val engine = FlowEngine(coroutineContext, clock)
+ val sink = SimNetworkSink(engine, capacity = 100.0)
+ val source = spyk(Source(engine))
+ val switch = SimNetworkSwitchVirtual(engine)
+ val consumer = source.consumer
+
+ switch.newPort().connect(sink)
+ switch.newPort().connect(source)
+
+ assertTrue(sink.isConnected)
+ assertTrue(source.isConnected)
+
+ verify { source.createConsumer() }
+ verify { consumer.onStart(any(), any()) }
+ }
+
+ @Test
+ fun testConnectClosedPort() = runBlockingSimulation {
+ val engine = FlowEngine(coroutineContext, clock)
+ val sink = SimNetworkSink(engine, capacity = 100.0)
+ val switch = SimNetworkSwitchVirtual(engine)
+
+ val port = switch.newPort()
+ port.close()
+
+ assertThrows<IllegalStateException> {
+ port.connect(sink)
+ }
+ }
+
+ private class Source(engine: FlowEngine) : SimNetworkPort() {
+ val consumer = spyk(FixedFlowSource(Double.POSITIVE_INFINITY, utilization = 0.8))
+
+ public override fun createConsumer(): FlowSource = consumer
+
+ override val provider: FlowConsumer = FlowSink(engine, 0.0)
+ }
+}
diff --git a/opendc-simulator/opendc-simulator-power/build.gradle.kts b/opendc-simulator/opendc-simulator-power/build.gradle.kts
new file mode 100644
index 00000000..5d8c8949
--- /dev/null
+++ b/opendc-simulator/opendc-simulator-power/build.gradle.kts
@@ -0,0 +1,37 @@
+/*
+ * 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.
+ */
+
+description = "Library for simulating datacenter power components"
+
+plugins {
+ `kotlin-library-conventions`
+ `testing-conventions`
+ `jacoco-conventions`
+}
+
+dependencies {
+ api(platform(projects.opendcPlatform))
+ api(projects.opendcSimulator.opendcSimulatorFlow)
+ implementation(projects.opendcSimulator.opendcSimulatorCore)
+
+ testImplementation(libs.slf4j.simple)
+}
diff --git a/opendc-simulator/opendc-simulator-power/src/main/kotlin/org/opendc/simulator/power/SimPdu.kt b/opendc-simulator/opendc-simulator-power/src/main/kotlin/org/opendc/simulator/power/SimPdu.kt
new file mode 100644
index 00000000..9f88fecc
--- /dev/null
+++ b/opendc-simulator/opendc-simulator-power/src/main/kotlin/org/opendc/simulator/power/SimPdu.kt
@@ -0,0 +1,92 @@
+/*
+ * 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.power
+
+import org.opendc.simulator.flow.*
+import org.opendc.simulator.flow.mux.FlowMultiplexer
+import org.opendc.simulator.flow.mux.MaxMinFlowMultiplexer
+
+/**
+ * A model of a Power Distribution Unit (PDU).
+ *
+ * @param engine The underlying [FlowEngine] to drive the simulation under the hood.
+ * @param idlePower The idle power consumption of the PDU independent of the load on the PDU.
+ * @param lossCoefficient The coefficient for the power loss of the PDU proportional to the square load.
+ */
+public class SimPdu(
+ engine: FlowEngine,
+ private val idlePower: Double = 0.0,
+ private val lossCoefficient: Double = 0.0,
+) : SimPowerInlet() {
+ /**
+ * The [FlowMultiplexer] that distributes the electricity over the PDU outlets.
+ */
+ private val mux = MaxMinFlowMultiplexer(engine)
+
+ /**
+ * The [FlowForwarder] that represents the input of the PDU.
+ */
+ private val output = mux.newOutput()
+
+ /**
+ * Create a new PDU outlet.
+ */
+ public fun newOutlet(): Outlet = Outlet(mux, mux.newInput())
+
+ override fun createSource(): FlowSource = FlowMapper(output) { _, rate ->
+ val loss = computePowerLoss(rate)
+ rate + loss
+ }
+
+ override fun toString(): String = "SimPdu"
+
+ /**
+ * Compute the power loss that occurs in the PDU.
+ */
+ private fun computePowerLoss(load: Double): Double {
+ // See https://download.schneider-electric.com/files?p_Doc_Ref=SPD_NRAN-66CK3D_EN
+ return idlePower + lossCoefficient * (load * load)
+ }
+
+ /**
+ * A PDU outlet.
+ */
+ public class Outlet(private val switch: FlowMultiplexer, private val provider: FlowConsumer) : SimPowerOutlet(), AutoCloseable {
+ override fun onConnect(inlet: SimPowerInlet) {
+ provider.startConsumer(inlet.createSource())
+ }
+
+ override fun onDisconnect(inlet: SimPowerInlet) {
+ provider.cancel()
+ }
+
+ /**
+ * Remove the outlet from the PDU.
+ */
+ override fun close() {
+ switch.removeInput(provider)
+ }
+
+ override fun toString(): String = "SimPdu.Outlet"
+ }
+}
diff --git a/opendc-simulator/opendc-simulator-power/src/main/kotlin/org/opendc/simulator/power/SimPowerInlet.kt b/opendc-simulator/opendc-simulator-power/src/main/kotlin/org/opendc/simulator/power/SimPowerInlet.kt
new file mode 100644
index 00000000..de587b7f
--- /dev/null
+++ b/opendc-simulator/opendc-simulator-power/src/main/kotlin/org/opendc/simulator/power/SimPowerInlet.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.power
+
+import org.opendc.simulator.flow.FlowSource
+
+/**
+ * An abstract inlet that consumes electricity from a power outlet.
+ */
+public abstract class SimPowerInlet {
+ /**
+ * A flag to indicate that the inlet is currently connected to an outlet.
+ */
+ public val isConnected: Boolean
+ get() = _outlet != null
+
+ /**
+ * The [SimPowerOutlet] to which the inlet is connected.
+ */
+ public val outlet: SimPowerOutlet?
+ get() = _outlet
+ internal var _outlet: SimPowerOutlet? = null
+
+ /**
+ * Create a [FlowSource] which represents the consumption of electricity from the power outlet.
+ */
+ public abstract fun createSource(): FlowSource
+}
diff --git a/opendc-simulator/opendc-simulator-power/src/main/kotlin/org/opendc/simulator/power/SimPowerOutlet.kt b/opendc-simulator/opendc-simulator-power/src/main/kotlin/org/opendc/simulator/power/SimPowerOutlet.kt
new file mode 100644
index 00000000..72f52acc
--- /dev/null
+++ b/opendc-simulator/opendc-simulator-power/src/main/kotlin/org/opendc/simulator/power/SimPowerOutlet.kt
@@ -0,0 +1,80 @@
+/*
+ * 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.power
+
+/**
+ * An abstract outlet that provides a source of electricity for datacenter components.
+ */
+public abstract class SimPowerOutlet {
+ /**
+ * A flag to indicate that the inlet is currently connected to an outlet.
+ */
+ public val isConnected: Boolean
+ get() = _inlet != null
+
+ /**
+ * The inlet that is connected to this outlet currently.
+ */
+ public val inlet: SimPowerInlet?
+ get() = _inlet
+ private var _inlet: SimPowerInlet? = null
+
+ /**
+ * Connect the specified power [inlet] to this outlet.
+ *
+ * @param inlet The inlet to connect to the outlet.
+ */
+ public fun connect(inlet: SimPowerInlet) {
+ check(!isConnected) { "Outlet already connected" }
+ check(!inlet.isConnected) { "Inlet already connected" }
+
+ _inlet = inlet
+ inlet._outlet = this
+
+ onConnect(inlet)
+ }
+
+ /**
+ * Disconnect the connected power outlet from this inlet
+ */
+ public fun disconnect() {
+ val inlet = _inlet
+ if (inlet != null) {
+ _inlet = null
+ assert(inlet._outlet == this) { "Inlet state incorrect" }
+ inlet._outlet = null
+
+ onDisconnect(inlet)
+ }
+ }
+
+ /**
+ * This method is invoked when an inlet is connected to the outlet.
+ */
+ protected abstract fun onConnect(inlet: SimPowerInlet)
+
+ /**
+ * This method is invoked when an inlet is disconnected from the outlet.
+ */
+ protected abstract fun onDisconnect(inlet: SimPowerInlet)
+}
diff --git a/opendc-simulator/opendc-simulator-resources/src/main/kotlin/org/opendc/simulator/resources/SimResourceContext.kt b/opendc-simulator/opendc-simulator-power/src/main/kotlin/org/opendc/simulator/power/SimPowerSource.kt
index 7c76c634..07e9f52e 100644
--- a/opendc-simulator/opendc-simulator-resources/src/main/kotlin/org/opendc/simulator/resources/SimResourceContext.kt
+++ b/opendc-simulator/opendc-simulator-power/src/main/kotlin/org/opendc/simulator/power/SimPowerSource.kt
@@ -20,37 +20,35 @@
* SOFTWARE.
*/
-package org.opendc.simulator.resources
+package org.opendc.simulator.power
-import java.time.Clock
+import org.opendc.simulator.flow.FlowEngine
+import org.opendc.simulator.flow.FlowSink
/**
- * The execution context in which a [SimResourceConsumer] runs. It facilitates the communication and control between a
- * resource and a resource consumer.
+ * A [SimPowerOutlet] that represents a source of electricity.
+ *
+ * @param engine The underlying [FlowEngine] to drive the simulation under the hood.
*/
-public interface SimResourceContext {
+public class SimPowerSource(engine: FlowEngine, public val capacity: Double) : SimPowerOutlet() {
/**
- * The virtual clock tracking simulation time.
+ * The resource source that drives this power source.
*/
- public val clock: Clock
+ private val source = FlowSink(engine, capacity)
/**
- * The resource capacity available at this instant.
+ * The power draw at this instant.
*/
- public val capacity: Double
+ public val powerDraw: Double
+ get() = source.rate
- /**
- * The resource processing speed at this instant.
- */
- public val speed: Double
+ override fun onConnect(inlet: SimPowerInlet) {
+ source.startConsumer(inlet.createSource())
+ }
- /**
- * The amount of work still remaining at this instant.
- */
- public val remainingWork: Double
+ override fun onDisconnect(inlet: SimPowerInlet) {
+ source.cancel()
+ }
- /**
- * Ask the resource provider to interrupt its resource.
- */
- public fun interrupt()
+ override fun toString(): String = "SimPowerSource"
}
diff --git a/opendc-simulator/opendc-simulator-power/src/main/kotlin/org/opendc/simulator/power/SimUps.kt b/opendc-simulator/opendc-simulator-power/src/main/kotlin/org/opendc/simulator/power/SimUps.kt
new file mode 100644
index 00000000..46d659f8
--- /dev/null
+++ b/opendc-simulator/opendc-simulator-power/src/main/kotlin/org/opendc/simulator/power/SimUps.kt
@@ -0,0 +1,98 @@
+/*
+ * 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.power
+
+import org.opendc.simulator.flow.*
+import org.opendc.simulator.flow.mux.MaxMinFlowMultiplexer
+
+/**
+ * A model of an Uninterruptible Power Supply (UPS).
+ *
+ * This model aggregates multiple power sources into a single source in order to ensure that power is always available.
+ *
+ * @param engine The underlying [FlowEngine] to drive the simulation under the hood.
+ * @param idlePower The idle power consumption of the UPS independent of the load.
+ * @param lossCoefficient The coefficient for the power loss of the UPS proportional to the load.
+ */
+public class SimUps(
+ private val engine: FlowEngine,
+ private val idlePower: Double = 0.0,
+ private val lossCoefficient: Double = 0.0,
+) : SimPowerOutlet() {
+ /**
+ * The resource aggregator used to combine the input sources.
+ */
+ private val mux = MaxMinFlowMultiplexer(engine)
+
+ /**
+ * The [FlowConsumer] that represents the output of the UPS.
+ */
+ private val provider = mux.newInput()
+
+ /**
+ * Create a new UPS outlet.
+ */
+ public fun newInlet(): SimPowerInlet {
+ val forward = FlowForwarder(engine, isCoupled = true)
+ forward.startConsumer(mux.newOutput())
+ return Inlet(forward)
+ }
+
+ override fun onConnect(inlet: SimPowerInlet) {
+ val source = inlet.createSource()
+ val mapper = FlowMapper(source) { _, rate ->
+ val loss = computePowerLoss(rate)
+ rate + loss
+ }
+
+ provider.startConsumer(mapper)
+ }
+
+ override fun onDisconnect(inlet: SimPowerInlet) {
+ provider.cancel()
+ }
+
+ /**
+ * Compute the power loss that occurs in the UPS.
+ */
+ private fun computePowerLoss(load: Double): Double {
+ // See https://download.schneider-electric.com/files?p_Doc_Ref=SPD_NRAN-66CK3D_EN
+ return idlePower + lossCoefficient * load
+ }
+
+ /**
+ * A UPS inlet.
+ */
+ public inner class Inlet(private val forwarder: FlowForwarder) : SimPowerInlet(), AutoCloseable {
+ override fun createSource(): FlowSource = forwarder
+
+ /**
+ * Remove the inlet from the PSU.
+ */
+ override fun close() {
+ forwarder.close()
+ }
+
+ override fun toString(): String = "SimPsu.Inlet"
+ }
+}
diff --git a/opendc-simulator/opendc-simulator-power/src/test/kotlin/org/opendc/simulator/power/SimPduTest.kt b/opendc-simulator/opendc-simulator-power/src/test/kotlin/org/opendc/simulator/power/SimPduTest.kt
new file mode 100644
index 00000000..eb823eb1
--- /dev/null
+++ b/opendc-simulator/opendc-simulator-power/src/test/kotlin/org/opendc/simulator/power/SimPduTest.kt
@@ -0,0 +1,119 @@
+/*
+ * 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.power
+
+import io.mockk.spyk
+import io.mockk.verify
+import org.junit.jupiter.api.Assertions.assertEquals
+import org.junit.jupiter.api.Test
+import org.junit.jupiter.api.assertThrows
+import org.opendc.simulator.core.runBlockingSimulation
+import org.opendc.simulator.flow.FlowEngine
+import org.opendc.simulator.flow.FlowSource
+import org.opendc.simulator.flow.source.FixedFlowSource
+
+/**
+ * Test suite for the [SimPdu] class.
+ */
+internal class SimPduTest {
+ @Test
+ fun testZeroOutlets() = runBlockingSimulation {
+ val engine = FlowEngine(coroutineContext, clock)
+ val source = SimPowerSource(engine, capacity = 100.0)
+ val pdu = SimPdu(engine)
+ source.connect(pdu)
+
+ assertEquals(0.0, source.powerDraw)
+ }
+
+ @Test
+ fun testSingleOutlet() = runBlockingSimulation {
+ val engine = FlowEngine(coroutineContext, clock)
+ val source = SimPowerSource(engine, capacity = 100.0)
+ val pdu = SimPdu(engine)
+ source.connect(pdu)
+ pdu.newOutlet().connect(SimpleInlet())
+
+ assertEquals(50.0, source.powerDraw)
+ }
+
+ @Test
+ fun testDoubleOutlet() = runBlockingSimulation {
+ val engine = FlowEngine(coroutineContext, clock)
+ val source = SimPowerSource(engine, capacity = 100.0)
+ val pdu = SimPdu(engine)
+ source.connect(pdu)
+
+ pdu.newOutlet().connect(SimpleInlet())
+ pdu.newOutlet().connect(SimpleInlet())
+
+ assertEquals(100.0, source.powerDraw)
+ }
+
+ @Test
+ fun testDisconnect() = runBlockingSimulation {
+ val engine = FlowEngine(coroutineContext, clock)
+ val source = SimPowerSource(engine, capacity = 100.0)
+ val pdu = SimPdu(engine)
+ source.connect(pdu)
+ val consumer = spyk(FixedFlowSource(100.0, utilization = 1.0))
+ val inlet = object : SimPowerInlet() {
+ override fun createSource(): FlowSource = consumer
+ }
+
+ val outlet = pdu.newOutlet()
+ outlet.connect(inlet)
+ outlet.disconnect()
+
+ verify { consumer.onStop(any(), any(), any()) }
+ }
+
+ @Test
+ fun testLoss() = runBlockingSimulation {
+ val engine = FlowEngine(coroutineContext, clock)
+ val source = SimPowerSource(engine, capacity = 100.0)
+ // https://download.schneider-electric.com/files?p_Doc_Ref=SPD_NRAN-66CK3D_EN
+ val pdu = SimPdu(engine, idlePower = 1.5, lossCoefficient = 0.015)
+ source.connect(pdu)
+ pdu.newOutlet().connect(SimpleInlet())
+ assertEquals(89.0, source.powerDraw, 0.01)
+ }
+
+ @Test
+ fun testOutletClose() = runBlockingSimulation {
+ val engine = FlowEngine(coroutineContext, clock)
+ val source = SimPowerSource(engine, capacity = 100.0)
+ val pdu = SimPdu(engine)
+ source.connect(pdu)
+ val outlet = pdu.newOutlet()
+ outlet.close()
+
+ assertThrows<IllegalStateException> {
+ outlet.connect(SimpleInlet())
+ }
+ }
+
+ class SimpleInlet : SimPowerInlet() {
+ override fun createSource(): FlowSource = FixedFlowSource(100.0, utilization = 0.5)
+ }
+}
diff --git a/opendc-simulator/opendc-simulator-power/src/test/kotlin/org/opendc/simulator/power/SimPowerSourceTest.kt b/opendc-simulator/opendc-simulator-power/src/test/kotlin/org/opendc/simulator/power/SimPowerSourceTest.kt
new file mode 100644
index 00000000..76142103
--- /dev/null
+++ b/opendc-simulator/opendc-simulator-power/src/test/kotlin/org/opendc/simulator/power/SimPowerSourceTest.kt
@@ -0,0 +1,136 @@
+/*
+ * 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.power
+
+import io.mockk.every
+import io.mockk.mockk
+import io.mockk.spyk
+import io.mockk.verify
+import org.junit.jupiter.api.Assertions.*
+import org.junit.jupiter.api.Test
+import org.junit.jupiter.api.assertDoesNotThrow
+import org.junit.jupiter.api.assertThrows
+import org.opendc.simulator.core.runBlockingSimulation
+import org.opendc.simulator.flow.FlowEngine
+import org.opendc.simulator.flow.FlowSource
+import org.opendc.simulator.flow.source.FixedFlowSource
+
+/**
+ * Test suite for the [SimPowerSource]
+ */
+internal class SimPowerSourceTest {
+ @Test
+ fun testInitialState() = runBlockingSimulation {
+ val engine = FlowEngine(coroutineContext, clock)
+ val source = SimPowerSource(engine, capacity = 100.0)
+
+ assertFalse(source.isConnected)
+ assertNull(source.inlet)
+ assertEquals(100.0, source.capacity)
+ }
+
+ @Test
+ fun testDisconnectIdempotent() = runBlockingSimulation {
+ val engine = FlowEngine(coroutineContext, clock)
+ val source = SimPowerSource(engine, capacity = 100.0)
+
+ assertDoesNotThrow { source.disconnect() }
+ assertFalse(source.isConnected)
+ }
+
+ @Test
+ fun testConnect() = runBlockingSimulation {
+ val engine = FlowEngine(coroutineContext, clock)
+ val source = SimPowerSource(engine, capacity = 100.0)
+ val inlet = SimpleInlet()
+
+ source.connect(inlet)
+
+ assertTrue(source.isConnected)
+ assertEquals(inlet, source.inlet)
+ assertTrue(inlet.isConnected)
+ assertEquals(source, inlet.outlet)
+ assertEquals(100.0, source.powerDraw)
+ }
+
+ @Test
+ fun testDisconnect() = runBlockingSimulation {
+ val engine = FlowEngine(coroutineContext, clock)
+ val source = SimPowerSource(engine, capacity = 100.0)
+ val consumer = spyk(FixedFlowSource(100.0, utilization = 1.0))
+ val inlet = object : SimPowerInlet() {
+ override fun createSource(): FlowSource = consumer
+ }
+
+ source.connect(inlet)
+ source.disconnect()
+
+ verify { consumer.onStop(any(), any(), any()) }
+ }
+
+ @Test
+ fun testDisconnectAssertion() = runBlockingSimulation {
+ val engine = FlowEngine(coroutineContext, clock)
+ val source = SimPowerSource(engine, capacity = 100.0)
+ val inlet = mockk<SimPowerInlet>(relaxUnitFun = true)
+ every { inlet.isConnected } returns false
+ every { inlet._outlet } returns null
+ every { inlet.createSource() } returns FixedFlowSource(100.0, utilization = 1.0)
+
+ source.connect(inlet)
+
+ assertThrows<AssertionError> {
+ source.disconnect()
+ }
+ }
+
+ @Test
+ fun testOutletAlreadyConnected() = runBlockingSimulation {
+ val engine = FlowEngine(coroutineContext, clock)
+ val source = SimPowerSource(engine, capacity = 100.0)
+ val inlet = SimpleInlet()
+
+ source.connect(inlet)
+ assertThrows<IllegalStateException> {
+ source.connect(SimpleInlet())
+ }
+
+ assertEquals(inlet, source.inlet)
+ }
+
+ @Test
+ fun testInletAlreadyConnected() = runBlockingSimulation {
+ val engine = FlowEngine(coroutineContext, clock)
+ val source = SimPowerSource(engine, capacity = 100.0)
+ val inlet = mockk<SimPowerInlet>(relaxUnitFun = true)
+ every { inlet.isConnected } returns true
+
+ assertThrows<IllegalStateException> {
+ source.connect(inlet)
+ }
+ }
+
+ class SimpleInlet : SimPowerInlet() {
+ override fun createSource(): FlowSource = FixedFlowSource(100.0, utilization = 1.0)
+ }
+}
diff --git a/opendc-simulator/opendc-simulator-power/src/test/kotlin/org/opendc/simulator/power/SimUpsTest.kt b/opendc-simulator/opendc-simulator-power/src/test/kotlin/org/opendc/simulator/power/SimUpsTest.kt
new file mode 100644
index 00000000..a764a368
--- /dev/null
+++ b/opendc-simulator/opendc-simulator-power/src/test/kotlin/org/opendc/simulator/power/SimUpsTest.kt
@@ -0,0 +1,101 @@
+/*
+ * 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.power
+
+import io.mockk.spyk
+import io.mockk.verify
+import org.junit.jupiter.api.Assertions.assertAll
+import org.junit.jupiter.api.Assertions.assertEquals
+import org.junit.jupiter.api.Test
+import org.opendc.simulator.core.runBlockingSimulation
+import org.opendc.simulator.flow.FlowEngine
+import org.opendc.simulator.flow.FlowSource
+import org.opendc.simulator.flow.source.FixedFlowSource
+
+/**
+ * Test suite for the [SimUps] class.
+ */
+internal class SimUpsTest {
+ @Test
+ fun testSingleInlet() = runBlockingSimulation {
+ val engine = FlowEngine(coroutineContext, clock)
+ val source = SimPowerSource(engine, capacity = 100.0)
+ val ups = SimUps(engine)
+ source.connect(ups.newInlet())
+ ups.connect(SimpleInlet())
+
+ assertEquals(50.0, source.powerDraw)
+ }
+
+ @Test
+ fun testDoubleInlet() = runBlockingSimulation {
+ val engine = FlowEngine(coroutineContext, clock)
+ val source1 = SimPowerSource(engine, capacity = 100.0)
+ val source2 = SimPowerSource(engine, capacity = 100.0)
+ val ups = SimUps(engine)
+ source1.connect(ups.newInlet())
+ source2.connect(ups.newInlet())
+
+ ups.connect(SimpleInlet())
+
+ assertAll(
+ { assertEquals(50.0, source1.powerDraw) },
+ { assertEquals(50.0, source2.powerDraw) }
+ )
+ }
+
+ @Test
+ fun testLoss() = runBlockingSimulation {
+ val engine = FlowEngine(coroutineContext, clock)
+ val source = SimPowerSource(engine, capacity = 100.0)
+ // https://download.schneider-electric.com/files?p_Doc_Ref=SPD_NRAN-66CK3D_EN
+ val ups = SimUps(engine, idlePower = 4.0, lossCoefficient = 0.05)
+ source.connect(ups.newInlet())
+ ups.connect(SimpleInlet())
+
+ assertEquals(56.5, source.powerDraw)
+ }
+
+ @Test
+ fun testDisconnect() = runBlockingSimulation {
+ val engine = FlowEngine(coroutineContext, clock)
+ val source1 = SimPowerSource(engine, capacity = 100.0)
+ val source2 = SimPowerSource(engine, capacity = 100.0)
+ val ups = SimUps(engine)
+ source1.connect(ups.newInlet())
+ source2.connect(ups.newInlet())
+ val consumer = spyk(FixedFlowSource(100.0, utilization = 1.0))
+ val inlet = object : SimPowerInlet() {
+ override fun createSource(): FlowSource = consumer
+ }
+
+ ups.connect(inlet)
+ ups.disconnect()
+
+ verify { consumer.onStop(any(), any(), any()) }
+ }
+
+ class SimpleInlet : SimPowerInlet() {
+ override fun createSource(): FlowSource = FixedFlowSource(100.0, utilization = 0.5)
+ }
+}
diff --git a/opendc-simulator/opendc-simulator-resources/src/jmh/kotlin/org/opendc/simulator/resources/SimResourceBenchmarks.kt b/opendc-simulator/opendc-simulator-resources/src/jmh/kotlin/org/opendc/simulator/resources/SimResourceBenchmarks.kt
deleted file mode 100644
index cd5f33bd..00000000
--- a/opendc-simulator/opendc-simulator-resources/src/jmh/kotlin/org/opendc/simulator/resources/SimResourceBenchmarks.kt
+++ /dev/null
@@ -1,144 +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.resources
-
-import kotlinx.coroutines.ExperimentalCoroutinesApi
-import kotlinx.coroutines.launch
-import org.opendc.simulator.core.SimulationCoroutineScope
-import org.opendc.simulator.core.runBlockingSimulation
-import org.opendc.simulator.resources.consumer.SimTraceConsumer
-import org.openjdk.jmh.annotations.*
-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 SimResourceBenchmarks {
- private lateinit var scope: SimulationCoroutineScope
- private lateinit var scheduler: SimResourceScheduler
-
- @Setup
- fun setUp() {
- scope = SimulationCoroutineScope()
- scheduler = SimResourceSchedulerTrampoline(scope.coroutineContext, scope.clock)
- }
-
- @State(Scope.Thread)
- class Workload {
- lateinit var trace: Sequence<SimTraceConsumer.Fragment>
-
- @Setup
- fun setUp() {
- trace = sequenceOf(
- SimTraceConsumer.Fragment(1000, 28.0),
- SimTraceConsumer.Fragment(1000, 3500.0),
- SimTraceConsumer.Fragment(1000, 0.0),
- SimTraceConsumer.Fragment(1000, 183.0),
- SimTraceConsumer.Fragment(1000, 400.0),
- SimTraceConsumer.Fragment(1000, 100.0),
- SimTraceConsumer.Fragment(1000, 3000.0),
- SimTraceConsumer.Fragment(1000, 4500.0),
- )
- }
- }
-
- @Benchmark
- fun benchmarkSource(state: Workload) {
- return scope.runBlockingSimulation {
- val provider = SimResourceSource(4200.0, scheduler)
- return@runBlockingSimulation provider.consume(SimTraceConsumer(state.trace))
- }
- }
-
- @Benchmark
- fun benchmarkForwardOverhead(state: Workload) {
- return scope.runBlockingSimulation {
- val provider = SimResourceSource(4200.0, scheduler)
- val forwarder = SimResourceForwarder()
- provider.startConsumer(forwarder)
- return@runBlockingSimulation forwarder.consume(SimTraceConsumer(state.trace))
- }
- }
-
- @Benchmark
- fun benchmarkSwitchMaxMinSingleConsumer(state: Workload) {
- return scope.runBlockingSimulation {
- val switch = SimResourceSwitchMaxMin(scheduler)
-
- switch.addInput(SimResourceSource(3000.0, scheduler))
- switch.addInput(SimResourceSource(3000.0, scheduler))
-
- val provider = switch.addOutput(3500.0)
- return@runBlockingSimulation provider.consume(SimTraceConsumer(state.trace))
- }
- }
-
- @Benchmark
- fun benchmarkSwitchMaxMinTripleConsumer(state: Workload) {
- return scope.runBlockingSimulation {
- val switch = SimResourceSwitchMaxMin(scheduler)
-
- switch.addInput(SimResourceSource(3000.0, scheduler))
- switch.addInput(SimResourceSource(3000.0, scheduler))
-
- repeat(3) { i ->
- launch {
- val provider = switch.addOutput(3500.0)
- provider.consume(SimTraceConsumer(state.trace))
- }
- }
- }
- }
-
- @Benchmark
- fun benchmarkSwitchExclusiveSingleConsumer(state: Workload) {
- return scope.runBlockingSimulation {
- val switch = SimResourceSwitchExclusive()
-
- switch.addInput(SimResourceSource(3000.0, scheduler))
- switch.addInput(SimResourceSource(3000.0, scheduler))
-
- val provider = switch.addOutput(3500.0)
- return@runBlockingSimulation provider.consume(SimTraceConsumer(state.trace))
- }
- }
-
- @Benchmark
- fun benchmarkSwitchExclusiveTripleConsumer(state: Workload) {
- return scope.runBlockingSimulation {
- val switch = SimResourceSwitchExclusive()
-
- switch.addInput(SimResourceSource(3000.0, scheduler))
- switch.addInput(SimResourceSource(3000.0, scheduler))
-
- repeat(2) {
- launch {
- val provider = switch.addOutput(3500.0)
- provider.consume(SimTraceConsumer(state.trace))
- }
- }
- }
- }
-}
diff --git a/opendc-simulator/opendc-simulator-resources/src/main/kotlin/org/opendc/simulator/resources/SimAbstractResourceAggregator.kt b/opendc-simulator/opendc-simulator-resources/src/main/kotlin/org/opendc/simulator/resources/SimAbstractResourceAggregator.kt
deleted file mode 100644
index 6ae04f27..00000000
--- a/opendc-simulator/opendc-simulator-resources/src/main/kotlin/org/opendc/simulator/resources/SimAbstractResourceAggregator.kt
+++ /dev/null
@@ -1,178 +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.resources
-
-/**
- * Abstract implementation of [SimResourceAggregator].
- */
-public abstract class SimAbstractResourceAggregator(private val scheduler: SimResourceScheduler) : SimResourceAggregator {
- /**
- * This method is invoked when the resource consumer consumes resources.
- */
- protected abstract fun doConsume(work: Double, limit: Double, deadline: Long)
-
- /**
- * This method is invoked when the resource consumer enters an idle state.
- */
- protected abstract fun doIdle(deadline: Long)
-
- /**
- * This method is invoked when the resource consumer finishes processing.
- */
- protected abstract fun doFinish(cause: Throwable?)
-
- /**
- * This method is invoked when an input context is started.
- */
- protected abstract fun onInputStarted(input: Input)
-
- /**
- * This method is invoked when an input is stopped.
- */
- protected abstract fun onInputFinished(input: Input)
-
- override fun addInput(input: SimResourceProvider) {
- check(output.state != SimResourceState.Stopped) { "Aggregator has been stopped" }
-
- val consumer = Consumer()
- _inputs.add(input)
- _inputConsumers.add(consumer)
- input.startConsumer(consumer)
- }
-
- override fun close() {
- output.close()
- }
-
- override val output: SimResourceProvider
- get() = _output
- private val _output = SimResourceForwarder()
-
- override val inputs: Set<SimResourceProvider>
- get() = _inputs
- private val _inputs = mutableSetOf<SimResourceProvider>()
- private val _inputConsumers = mutableListOf<Consumer>()
-
- protected val outputContext: SimResourceContext
- get() = context
- private val context = object : SimAbstractResourceContext(0.0, scheduler, _output) {
- override val remainingWork: Double
- get() {
- val now = clock.millis()
-
- return if (_remainingWorkFlush < now) {
- _remainingWorkFlush = now
- _inputConsumers.sumByDouble { it._ctx?.remainingWork ?: 0.0 }.also { _remainingWork = it }
- } else {
- _remainingWork
- }
- }
- private var _remainingWork: Double = 0.0
- private var _remainingWorkFlush: Long = Long.MIN_VALUE
-
- override fun onConsume(work: Double, limit: Double, deadline: Long) = doConsume(work, limit, deadline)
-
- override fun onIdle(deadline: Long) = doIdle(deadline)
-
- override fun onFinish() {
- doFinish(null)
- }
- }
-
- /**
- * An input for the resource aggregator.
- */
- public interface Input {
- /**
- * The [SimResourceContext] associated with the input.
- */
- public val ctx: SimResourceContext
-
- /**
- * Push the specified [SimResourceCommand] to the input.
- */
- public fun push(command: SimResourceCommand)
- }
-
- /**
- * An internal [SimResourceConsumer] implementation for aggregator inputs.
- */
- private inner class Consumer : Input, SimResourceConsumer {
- /**
- * The resource context associated with the input.
- */
- override val ctx: SimResourceContext
- get() = _ctx!!
- var _ctx: SimResourceContext? = null
-
- /**
- * The resource command to run next.
- */
- private var command: SimResourceCommand? = null
-
- private fun updateCapacity() {
- // Adjust capacity of output resource
- context.capacity = _inputConsumers.sumByDouble { it._ctx?.capacity ?: 0.0 }
- }
-
- /* Input */
- override fun push(command: SimResourceCommand) {
- this.command = command
- _ctx?.interrupt()
- }
-
- /* SimResourceConsumer */
- override fun onNext(ctx: SimResourceContext): SimResourceCommand {
- var next = command
-
- return if (next != null) {
- this.command = null
- next
- } else {
- context.flush(isIntermediate = true)
- next = command
- this.command = null
- next ?: SimResourceCommand.Idle()
- }
- }
-
- override fun onEvent(ctx: SimResourceContext, event: SimResourceEvent) {
- when (event) {
- SimResourceEvent.Start -> {
- _ctx = ctx
- updateCapacity()
-
- // Make sure we initialize the output if we have not done so yet
- if (context.state == SimResourceState.Pending) {
- context.start()
- }
-
- onInputStarted(this)
- }
- SimResourceEvent.Capacity -> updateCapacity()
- SimResourceEvent.Exit -> onInputFinished(this)
- else -> {}
- }
- }
- }
-}
diff --git a/opendc-simulator/opendc-simulator-resources/src/main/kotlin/org/opendc/simulator/resources/SimAbstractResourceContext.kt b/opendc-simulator/opendc-simulator-resources/src/main/kotlin/org/opendc/simulator/resources/SimAbstractResourceContext.kt
deleted file mode 100644
index c03bfad5..00000000
--- a/opendc-simulator/opendc-simulator-resources/src/main/kotlin/org/opendc/simulator/resources/SimAbstractResourceContext.kt
+++ /dev/null
@@ -1,362 +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.resources
-
-import java.time.Clock
-import kotlin.math.max
-import kotlin.math.min
-
-/**
- * Partial implementation of a [SimResourceContext] managing the communication between resources and resource consumers.
- */
-public abstract class SimAbstractResourceContext(
- initialCapacity: Double,
- private val scheduler: SimResourceScheduler,
- private val consumer: SimResourceConsumer
-) : SimResourceContext, SimResourceFlushable {
-
- /**
- * The clock of the context.
- */
- public override val clock: Clock
- get() = scheduler.clock
-
- /**
- * The capacity of the resource.
- */
- public final override var capacity: Double = initialCapacity
- set(value) {
- val oldValue = field
-
- // Only changes will be propagated
- if (value != oldValue) {
- field = value
- onCapacityChange()
- }
- }
-
- /**
- * The amount of work still remaining at this instant.
- */
- override val remainingWork: Double
- get() {
- val activeCommand = activeCommand ?: return 0.0
- val now = clock.millis()
-
- return if (_remainingWorkFlush < now) {
- _remainingWorkFlush = now
- computeRemainingWork(activeCommand, now).also { _remainingWork = it }
- } else {
- _remainingWork
- }
- }
- private var _remainingWork: Double = 0.0
- private var _remainingWorkFlush: Long = Long.MIN_VALUE
-
- /**
- * A flag to indicate the state of the context.
- */
- public var state: SimResourceState = SimResourceState.Pending
- private set
-
- /**
- * The current processing speed of the resource.
- */
- final override var speed: Double = 0.0
- private set
-
- /**
- * This method is invoked when the resource will idle until the specified [deadline].
- */
- public abstract fun onIdle(deadline: Long)
-
- /**
- * This method is invoked when the resource will be consumed until the specified [work] was processed or the
- * [deadline] was reached.
- */
- public abstract fun onConsume(work: Double, limit: Double, deadline: Long)
-
- /**
- * This method is invoked when the resource consumer has finished.
- */
- public abstract fun onFinish()
-
- /**
- * Get the remaining work to process after a resource consumption.
- *
- * @param work The size of the resource consumption.
- * @param speed The speed of consumption.
- * @param duration The duration from the start of the consumption until now.
- * @return The amount of work remaining.
- */
- protected open fun getRemainingWork(work: Double, speed: Double, duration: Long): Double {
- return if (duration > 0L) {
- val processed = duration / 1000.0 * speed
- max(0.0, work - processed)
- } else {
- 0.0
- }
- }
-
- /**
- * Start the consumer.
- */
- public fun start() {
- check(state == SimResourceState.Pending) { "Consumer is already started" }
-
- val now = clock.millis()
-
- state = SimResourceState.Active
- isProcessing = true
- latestFlush = now
-
- try {
- consumer.onEvent(this, SimResourceEvent.Start)
- activeCommand = interpret(consumer.onNext(this), now)
- } catch (cause: Throwable) {
- doFail(cause)
- } finally {
- isProcessing = false
- }
- }
-
- /**
- * Immediately stop the consumer.
- */
- public fun stop() {
- try {
- isProcessing = true
- latestFlush = clock.millis()
-
- flush(isIntermediate = true)
- doStop()
- } finally {
- isProcessing = false
- }
- }
-
- override fun flush(isIntermediate: Boolean) {
- // Flush is no-op when the consumer is finished or not yet started
- if (state != SimResourceState.Active) {
- return
- }
-
- val now = clock.millis()
-
- // Fast path: if the intermediate progress was already flushed at the current instant, we can skip it.
- if (isIntermediate && latestFlush >= now) {
- return
- }
-
- try {
- val activeCommand = activeCommand ?: return
- val (timestamp, command) = activeCommand
-
- // Note: accessor is reliant on activeCommand being set
- val remainingWork = remainingWork
-
- isProcessing = true
-
- val duration = now - timestamp
- assert(duration >= 0) { "Flush in the past" }
-
- this.activeCommand = when (command) {
- is SimResourceCommand.Idle -> {
- // We should only continue processing the next command if:
- // 1. The resource consumer reached its deadline.
- // 2. The resource consumer should be interrupted (e.g., someone called .interrupt())
- if (command.deadline <= now || !isIntermediate) {
- next(now)
- } else {
- interpret(SimResourceCommand.Idle(command.deadline), now)
- }
- }
- is SimResourceCommand.Consume -> {
- // We should only continue processing the next command if:
- // 1. The resource consumption was finished.
- // 2. The resource capacity cannot satisfy the demand.
- // 4. The resource consumer should be interrupted (e.g., someone called .interrupt())
- if (remainingWork == 0.0 || command.deadline <= now || !isIntermediate) {
- next(now)
- } else {
- interpret(SimResourceCommand.Consume(remainingWork, command.limit, command.deadline), now)
- }
- }
- SimResourceCommand.Exit ->
- // Flush may not be called when the resource consumer has finished
- throw IllegalStateException()
- }
-
- // Flush remaining work cache
- _remainingWorkFlush = Long.MIN_VALUE
- } catch (cause: Throwable) {
- doFail(cause)
- } finally {
- latestFlush = now
- isProcessing = false
- }
- }
-
- override fun interrupt() {
- // Prevent users from interrupting the resource while they are constructing their next command, as this will
- // only lead to infinite recursion.
- if (isProcessing) {
- return
- }
-
- scheduler.schedule(this, isIntermediate = false)
- }
-
- override fun toString(): String = "SimAbstractResourceContext[capacity=$capacity]"
-
- /**
- * A flag to indicate that the resource is currently processing a command.
- */
- private var isProcessing: Boolean = false
-
- /**
- * The current command that is being processed.
- */
- private var activeCommand: CommandWrapper? = null
-
- /**
- * The latest timestamp at which the resource was flushed.
- */
- private var latestFlush: Long = Long.MIN_VALUE
-
- /**
- * Finish the consumer and resource provider.
- */
- private fun doStop() {
- val state = state
- this.state = SimResourceState.Stopped
-
- if (state == SimResourceState.Active) {
- activeCommand = null
- try {
- consumer.onEvent(this, SimResourceEvent.Exit)
- onFinish()
- } catch (cause: Throwable) {
- doFail(cause)
- }
- }
- }
-
- /**
- * Interpret the specified [SimResourceCommand] that was submitted by the resource consumer.
- */
- private fun interpret(command: SimResourceCommand, now: Long): CommandWrapper? {
- when (command) {
- is SimResourceCommand.Idle -> {
- val deadline = command.deadline
-
- require(deadline >= now) { "Deadline already passed" }
-
- speed = 0.0
-
- onIdle(deadline)
- consumer.onEvent(this, SimResourceEvent.Run)
- }
- is SimResourceCommand.Consume -> {
- val work = command.work
- val limit = command.limit
- val deadline = command.deadline
-
- require(deadline >= now) { "Deadline already passed" }
-
- speed = min(capacity, limit)
- onConsume(work, limit, deadline)
- consumer.onEvent(this, SimResourceEvent.Run)
- }
- is SimResourceCommand.Exit -> {
- speed = 0.0
-
- doStop()
-
- // No need to set the next active command
- return null
- }
- }
-
- return CommandWrapper(now, command)
- }
-
- /**
- * Request the workload for more work.
- */
- private fun next(now: Long): CommandWrapper? = interpret(consumer.onNext(this), now)
-
- /**
- * Compute the remaining work based on the specified [wrapper] and [timestamp][now].
- */
- private fun computeRemainingWork(wrapper: CommandWrapper, now: Long): Double {
- val (timestamp, command) = wrapper
- val duration = now - timestamp
- return when (command) {
- is SimResourceCommand.Consume -> getRemainingWork(command.work, speed, duration)
- is SimResourceCommand.Idle, SimResourceCommand.Exit -> 0.0
- }
- }
-
- /**
- * Fail the resource consumer.
- */
- private fun doFail(cause: Throwable) {
- state = SimResourceState.Stopped
- activeCommand = null
-
- try {
- consumer.onFailure(this, cause)
- } catch (e: Throwable) {
- e.addSuppressed(cause)
- e.printStackTrace()
- }
-
- onFinish()
- }
-
- /**
- * Indicate that the capacity of the resource has changed.
- */
- private fun onCapacityChange() {
- // Do not inform the consumer if it has not been started yet
- if (state != SimResourceState.Active) {
- return
- }
-
- val isThrottled = speed > capacity
-
- consumer.onEvent(this, SimResourceEvent.Capacity)
-
- // Optimization: only flush changes if the new capacity cannot satisfy the active resource command.
- // Alternatively, if the consumer already interrupts the resource, the fast-path will be taken in flush().
- if (isThrottled) {
- flush(isIntermediate = true)
- }
- }
-
- /**
- * This class wraps a [command] with the timestamp it was started and possibly the task associated with it.
- */
- private data class CommandWrapper(val timestamp: Long, val command: SimResourceCommand)
-}
diff --git a/opendc-simulator/opendc-simulator-resources/src/main/kotlin/org/opendc/simulator/resources/SimResourceAggregatorMaxMin.kt b/opendc-simulator/opendc-simulator-resources/src/main/kotlin/org/opendc/simulator/resources/SimResourceAggregatorMaxMin.kt
deleted file mode 100644
index 5665abd1..00000000
--- a/opendc-simulator/opendc-simulator-resources/src/main/kotlin/org/opendc/simulator/resources/SimResourceAggregatorMaxMin.kt
+++ /dev/null
@@ -1,71 +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.resources
-
-/**
- * A [SimResourceAggregator] that distributes the load equally across the input resources.
- */
-public class SimResourceAggregatorMaxMin(scheduler: SimResourceScheduler) : SimAbstractResourceAggregator(scheduler) {
- private val consumers = mutableListOf<Input>()
-
- override fun doConsume(work: Double, limit: Double, deadline: Long) {
- // Sort all consumers by their capacity
- consumers.sortWith(compareBy { it.ctx.capacity })
-
- // Divide the requests over the available capacity of the input resources fairly
- for (input in consumers) {
- val inputCapacity = input.ctx.capacity
- val fraction = inputCapacity / outputContext.capacity
- val grantedSpeed = limit * fraction
- val grantedWork = fraction * work
-
- val command = if (grantedWork > 0.0 && grantedSpeed > 0.0)
- SimResourceCommand.Consume(grantedWork, grantedSpeed, deadline)
- else
- SimResourceCommand.Idle(deadline)
- input.push(command)
- }
- }
-
- override fun doIdle(deadline: Long) {
- for (input in consumers) {
- input.push(SimResourceCommand.Idle(deadline))
- }
- }
-
- override fun doFinish(cause: Throwable?) {
- val iterator = consumers.iterator()
- for (input in iterator) {
- iterator.remove()
- input.push(SimResourceCommand.Exit)
- }
- }
-
- override fun onInputStarted(input: Input) {
- consumers.add(input)
- }
-
- override fun onInputFinished(input: Input) {
- consumers.remove(input)
- }
-}
diff --git a/opendc-simulator/opendc-simulator-resources/src/main/kotlin/org/opendc/simulator/resources/SimResourceCommand.kt b/opendc-simulator/opendc-simulator-resources/src/main/kotlin/org/opendc/simulator/resources/SimResourceCommand.kt
deleted file mode 100644
index f7f3fa4d..00000000
--- a/opendc-simulator/opendc-simulator-resources/src/main/kotlin/org/opendc/simulator/resources/SimResourceCommand.kt
+++ /dev/null
@@ -1,52 +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.resources
-
-/**
- * A SimResourceCommand communicates to a resource how it is consumed by a [SimResourceConsumer].
- */
-public sealed class SimResourceCommand {
- /**
- * A request to the resource to perform the specified amount of work before the given [deadline].
- *
- * @param work The amount of work to process.
- * @param limit The maximum amount of work to be processed per second.
- * @param deadline The instant at which the work needs to be fulfilled.
- */
- public data class Consume(val work: Double, val limit: Double, val deadline: Long = Long.MAX_VALUE) : SimResourceCommand() {
- init {
- require(work > 0) { "Amount of work must be positive" }
- require(limit > 0) { "Limit must be positive" }
- }
- }
-
- /**
- * An indication to the resource that the consumer will idle until the specified [deadline] or if it is interrupted.
- */
- public data class Idle(val deadline: Long = Long.MAX_VALUE) : SimResourceCommand()
-
- /**
- * An indication to the resource that the consumer has finished.
- */
- public object Exit : SimResourceCommand()
-}
diff --git a/opendc-simulator/opendc-simulator-resources/src/main/kotlin/org/opendc/simulator/resources/SimResourceConsumer.kt b/opendc-simulator/opendc-simulator-resources/src/main/kotlin/org/opendc/simulator/resources/SimResourceConsumer.kt
deleted file mode 100644
index 4d937514..00000000
--- a/opendc-simulator/opendc-simulator-resources/src/main/kotlin/org/opendc/simulator/resources/SimResourceConsumer.kt
+++ /dev/null
@@ -1,56 +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.resources
-
-/**
- * A [SimResourceConsumer] characterizes how a resource is consumed.
- *
- * Implementors of this interface should be considered stateful and must be assumed not to be re-usable (concurrently)
- * for multiple resource providers, unless explicitly said otherwise.
- */
-public interface SimResourceConsumer {
- /**
- * This method is invoked when a resource asks for the next [command][SimResourceCommand] to process, either because
- * the resource finished processing, reached its deadline or was interrupted.
- *
- * @param ctx The execution context in which the consumer runs.
- * @return The next command that the resource should execute.
- */
- public fun onNext(ctx: SimResourceContext): SimResourceCommand
-
- /**
- * This method is invoked when an event has occurred.
- *
- * @param ctx The execution context in which the consumer runs.
- * @param event The event that has occurred.
- */
- public fun onEvent(ctx: SimResourceContext, event: SimResourceEvent) {}
-
- /**
- * This method is invoked when a resource consumer throws an exception.
- *
- * @param ctx The execution context in which the consumer runs.
- * @param cause The cause of the failure.
- */
- public fun onFailure(ctx: SimResourceContext, cause: Throwable) {}
-}
diff --git a/opendc-simulator/opendc-simulator-resources/src/main/kotlin/org/opendc/simulator/resources/SimResourceDistributorMaxMin.kt b/opendc-simulator/opendc-simulator-resources/src/main/kotlin/org/opendc/simulator/resources/SimResourceDistributorMaxMin.kt
deleted file mode 100644
index a76cb1e3..00000000
--- a/opendc-simulator/opendc-simulator-resources/src/main/kotlin/org/opendc/simulator/resources/SimResourceDistributorMaxMin.kt
+++ /dev/null
@@ -1,427 +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.resources
-
-import kotlin.math.max
-import kotlin.math.min
-
-/**
- * A [SimResourceDistributor] that distributes the capacity of a resource over consumers using max-min fair sharing.
- */
-public class SimResourceDistributorMaxMin(
- override val input: SimResourceProvider,
- private val scheduler: SimResourceScheduler,
- private val listener: Listener? = null
-) : SimResourceDistributor {
- override val outputs: Set<SimResourceProvider>
- get() = _outputs
- private val _outputs = mutableSetOf<OutputProvider>()
-
- /**
- * The active output contexts.
- */
- private val outputContexts: MutableList<OutputContext> = mutableListOf()
-
- /**
- * The total speed requested by the output resources.
- */
- private var totalRequestedSpeed = 0.0
-
- /**
- * The total amount of work requested by the output resources.
- */
- private var totalRequestedWork = 0.0
-
- /**
- * The total allocated speed for the output resources.
- */
- private var totalAllocatedSpeed = 0.0
-
- /**
- * The total allocated work requested for the output resources.
- */
- private var totalAllocatedWork = 0.0
-
- /**
- * The amount of work that could not be performed due to over-committing resources.
- */
- private var totalOvercommittedWork = 0.0
-
- /**
- * The amount of work that was lost due to interference.
- */
- private var totalInterferedWork = 0.0
-
- /**
- * A flag to indicate that the switch is closed.
- */
- private var isClosed: Boolean = false
-
- /**
- * An internal [SimResourceConsumer] implementation for switch inputs.
- */
- private val consumer = object : SimResourceConsumer {
- /**
- * The resource context of the consumer.
- */
- private lateinit var ctx: SimResourceContext
-
- val remainingWork: Double
- get() = ctx.remainingWork
-
- override fun onNext(ctx: SimResourceContext): SimResourceCommand {
- return doNext(ctx.capacity)
- }
-
- override fun onEvent(ctx: SimResourceContext, event: SimResourceEvent) {
- when (event) {
- SimResourceEvent.Start -> {
- this.ctx = ctx
- }
- SimResourceEvent.Exit -> {
- val iterator = _outputs.iterator()
- while (iterator.hasNext()) {
- val output = iterator.next()
-
- // Remove the output from the outputs to prevent ConcurrentModificationException when removing it
- // during the call to output.close()
- iterator.remove()
-
- output.close()
- }
- }
- else -> {}
- }
- }
- }
-
- /**
- * The total amount of remaining work.
- */
- private val totalRemainingWork: Double
- get() = consumer.remainingWork
-
- override fun addOutput(capacity: Double): SimResourceProvider {
- check(!isClosed) { "Distributor has been closed" }
-
- val provider = OutputProvider(capacity)
- _outputs.add(provider)
- return provider
- }
-
- override fun close() {
- if (!isClosed) {
- isClosed = true
- input.cancel()
- }
- }
-
- init {
- input.startConsumer(consumer)
- }
-
- /**
- * Indicate that the workloads should be re-scheduled.
- */
- private fun schedule() {
- input.interrupt()
- }
-
- /**
- * Schedule the work over the physical CPUs.
- */
- private fun doSchedule(capacity: Double): SimResourceCommand {
- // If there is no work yet, mark all inputs as idle.
- if (outputContexts.isEmpty()) {
- return SimResourceCommand.Idle()
- }
-
- val maxUsage = capacity
- var duration: Double = Double.MAX_VALUE
- var deadline: Long = Long.MAX_VALUE
- var availableSpeed = maxUsage
- var totalRequestedSpeed = 0.0
- var totalRequestedWork = 0.0
-
- // Flush the work of the outputs
- var outputIterator = outputContexts.listIterator()
- while (outputIterator.hasNext()) {
- val output = outputIterator.next()
-
- output.flush(isIntermediate = true)
-
- if (output.activeCommand == SimResourceCommand.Exit) {
- // Apparently the output consumer has exited, so remove it from the scheduling queue.
- outputIterator.remove()
- }
- }
-
- // Sort the outputs based on their requested usage
- // Profiling shows that it is faster to sort every slice instead of maintaining some kind of sorted set
- outputContexts.sort()
-
- // Divide the available input capacity fairly across the outputs using max-min fair sharing
- outputIterator = outputContexts.listIterator()
- var remaining = outputContexts.size
- while (outputIterator.hasNext()) {
- val output = outputIterator.next()
- val availableShare = availableSpeed / remaining--
-
- when (val command = output.activeCommand) {
- is SimResourceCommand.Idle -> {
- // Take into account the minimum deadline of this slice before we possible continue
- deadline = min(deadline, command.deadline)
-
- output.actualSpeed = 0.0
- }
- is SimResourceCommand.Consume -> {
- val grantedSpeed = min(output.allowedSpeed, availableShare)
-
- // Take into account the minimum deadline of this slice before we possible continue
- deadline = min(deadline, command.deadline)
-
- // Ignore idle computation
- if (grantedSpeed <= 0.0 || command.work <= 0.0) {
- output.actualSpeed = 0.0
- continue
- }
-
- totalRequestedSpeed += command.limit
- totalRequestedWork += command.work
-
- output.actualSpeed = grantedSpeed
- availableSpeed -= grantedSpeed
-
- // The duration that we want to run is that of the shortest request from an output
- duration = min(duration, command.work / grantedSpeed)
- }
- SimResourceCommand.Exit -> assert(false) { "Did not expect output to be stopped" }
- }
- }
-
- assert(deadline >= scheduler.clock.millis()) { "Deadline already passed" }
-
- this.totalRequestedSpeed = totalRequestedSpeed
- this.totalRequestedWork = totalRequestedWork
- this.totalAllocatedSpeed = maxUsage - availableSpeed
- this.totalAllocatedWork = min(totalRequestedWork, totalAllocatedSpeed * duration)
-
- return if (totalAllocatedWork > 0.0 && totalAllocatedSpeed > 0.0)
- SimResourceCommand.Consume(totalAllocatedWork, totalAllocatedSpeed, deadline)
- else
- SimResourceCommand.Idle(deadline)
- }
-
- /**
- * Obtain the next command to perform.
- */
- private fun doNext(capacity: Double): SimResourceCommand {
- val totalRequestedWork = totalRequestedWork.toLong()
- val totalRemainingWork = totalRemainingWork.toLong()
- val totalAllocatedWork = totalAllocatedWork.toLong()
- val totalRequestedSpeed = totalRequestedSpeed
- val totalAllocatedSpeed = totalAllocatedSpeed
-
- // Force all inputs to re-schedule their work.
- val command = doSchedule(capacity)
-
- // Report metrics
- listener?.onSliceFinish(
- this,
- totalRequestedWork,
- totalAllocatedWork - totalRemainingWork,
- totalOvercommittedWork.toLong(),
- totalInterferedWork.toLong(),
- totalAllocatedSpeed,
- totalRequestedSpeed
- )
-
- totalInterferedWork = 0.0
- totalOvercommittedWork = 0.0
-
- return command
- }
-
- /**
- * Event listener for hypervisor events.
- */
- public interface Listener {
- /**
- * This method is invoked when a slice is finished.
- */
- public fun onSliceFinish(
- switch: SimResourceDistributor,
- requestedWork: Long,
- grantedWork: Long,
- overcommittedWork: Long,
- interferedWork: Long,
- cpuUsage: Double,
- cpuDemand: Double
- )
- }
-
- /**
- * An internal [SimResourceProvider] implementation for switch outputs.
- */
- private inner class OutputProvider(val capacity: Double) : SimResourceProvider {
- /**
- * The [OutputContext] that is currently running.
- */
- private var ctx: OutputContext? = null
-
- override var state: SimResourceState = SimResourceState.Pending
- internal set
-
- override fun startConsumer(consumer: SimResourceConsumer) {
- check(state == SimResourceState.Pending) { "Resource cannot be consumed" }
-
- val ctx = OutputContext(this, consumer)
- this.ctx = ctx
- this.state = SimResourceState.Active
- outputContexts += ctx
-
- ctx.start()
- schedule()
- }
-
- override fun close() {
- cancel()
-
- if (state != SimResourceState.Stopped) {
- state = SimResourceState.Stopped
- _outputs.remove(this)
- }
- }
-
- override fun interrupt() {
- ctx?.interrupt()
- }
-
- override fun cancel() {
- val ctx = ctx
- if (ctx != null) {
- this.ctx = null
- ctx.stop()
- }
-
- if (state != SimResourceState.Stopped) {
- state = SimResourceState.Pending
- }
- }
- }
-
- /**
- * A [SimAbstractResourceContext] for the output resources.
- */
- private inner class OutputContext(
- private val provider: OutputProvider,
- consumer: SimResourceConsumer
- ) : SimAbstractResourceContext(provider.capacity, scheduler, consumer), Comparable<OutputContext> {
- /**
- * The current command that is processed by the vCPU.
- */
- var activeCommand: SimResourceCommand = SimResourceCommand.Idle()
-
- /**
- * The processing speed that is allowed by the model constraints.
- */
- var allowedSpeed: Double = 0.0
-
- /**
- * The actual processing speed.
- */
- var actualSpeed: Double = 0.0
-
- private fun reportOvercommit() {
- val remainingWork = remainingWork
- totalOvercommittedWork += remainingWork
- }
-
- override fun onIdle(deadline: Long) {
- reportOvercommit()
-
- allowedSpeed = 0.0
- activeCommand = SimResourceCommand.Idle(deadline)
- }
-
- override fun onConsume(work: Double, limit: Double, deadline: Long) {
- reportOvercommit()
-
- allowedSpeed = speed
- activeCommand = SimResourceCommand.Consume(work, limit, deadline)
- }
-
- override fun onFinish() {
- reportOvercommit()
-
- activeCommand = SimResourceCommand.Exit
- provider.cancel()
- }
-
- override fun getRemainingWork(work: Double, speed: Double, duration: Long): Double {
- // Apply performance interference model
- val performanceScore = 1.0
-
- // Compute the remaining amount of work
- return if (work > 0.0) {
- // Compute the fraction of compute time allocated to the VM
- val fraction = actualSpeed / totalAllocatedSpeed
-
- // Compute the work that was actually granted to the VM.
- val processingAvailable = max(0.0, totalAllocatedWork - totalRemainingWork) * fraction
- val processed = processingAvailable * performanceScore
-
- val interferedWork = processingAvailable - processed
-
- totalInterferedWork += interferedWork
-
- max(0.0, work - processed)
- } else {
- 0.0
- }
- }
-
- private var isProcessing: Boolean = false
-
- override fun interrupt() {
- // Prevent users from interrupting the CPU while it is constructing its next command, this will only lead
- // to infinite recursion.
- if (isProcessing) {
- return
- }
-
- try {
- isProcessing = false
-
- super.interrupt()
-
- // Force the scheduler to re-schedule
- schedule()
- } finally {
- isProcessing = true
- }
- }
-
- override fun compareTo(other: OutputContext): Int = allowedSpeed.compareTo(other.allowedSpeed)
- }
-}
diff --git a/opendc-simulator/opendc-simulator-resources/src/main/kotlin/org/opendc/simulator/resources/SimResourceFlushable.kt b/opendc-simulator/opendc-simulator-resources/src/main/kotlin/org/opendc/simulator/resources/SimResourceFlushable.kt
deleted file mode 100644
index f6a1a42e..00000000
--- a/opendc-simulator/opendc-simulator-resources/src/main/kotlin/org/opendc/simulator/resources/SimResourceFlushable.kt
+++ /dev/null
@@ -1,37 +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.resources
-
-/**
- * An interface used by the [SimResourceScheduler] to flush the progress of resource consumer.
- */
-public interface SimResourceFlushable {
- /**
- * Flush the current active resource consumption.
- *
- * @param isIntermediate A flag to indicate that the intermediate progress of the resource consumer should be
- * flushed, but without interrupting the resource consumer to submit a new command. If false, the resource consumer
- * will be asked to deliver a new command and is essentially interrupted.
- */
- public fun flush(isIntermediate: Boolean)
-}
diff --git a/opendc-simulator/opendc-simulator-resources/src/main/kotlin/org/opendc/simulator/resources/SimResourceProvider.kt b/opendc-simulator/opendc-simulator-resources/src/main/kotlin/org/opendc/simulator/resources/SimResourceProvider.kt
deleted file mode 100644
index 2f567a5e..00000000
--- a/opendc-simulator/opendc-simulator-resources/src/main/kotlin/org/opendc/simulator/resources/SimResourceProvider.kt
+++ /dev/null
@@ -1,93 +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.resources
-
-import kotlinx.coroutines.suspendCancellableCoroutine
-import kotlin.coroutines.resume
-import kotlin.coroutines.resumeWithException
-
-/**
- * A [SimResourceProvider] provides some resource of type [R].
- */
-public interface SimResourceProvider : AutoCloseable {
- /**
- * The state of the resource.
- */
- public val state: SimResourceState
-
- /**
- * Start the specified [resource consumer][consumer] in the context of this resource provider asynchronously.
- *
- * @throws IllegalStateException if there is already a consumer active or the resource lifetime has ended.
- */
- public fun startConsumer(consumer: SimResourceConsumer)
-
- /**
- * Interrupt the resource consumer. If there is no consumer active, this operation will be a no-op.
- */
- public fun interrupt()
-
- /**
- * Cancel the current resource consumer. If there is no consumer active, this operation will be a no-op.
- */
- public fun cancel()
-
- /**
- * End the lifetime of the resource.
- *
- * This operation terminates the existing resource consumer.
- */
- public override fun close()
-}
-
-/**
- * Consume the resource provided by this provider using the specified [consumer] and suspend execution until
- * the consumer has finished.
- */
-public suspend fun SimResourceProvider.consume(consumer: SimResourceConsumer) {
- return suspendCancellableCoroutine { cont ->
- startConsumer(object : SimResourceConsumer by consumer {
- override fun onEvent(ctx: SimResourceContext, event: SimResourceEvent) {
- consumer.onEvent(ctx, event)
-
- if (event == SimResourceEvent.Exit && !cont.isCompleted) {
- cont.resume(Unit)
- }
- }
-
- override fun onFailure(ctx: SimResourceContext, cause: Throwable) {
- try {
- consumer.onFailure(ctx, cause)
- cont.resumeWithException(cause)
- } catch (e: Throwable) {
- e.addSuppressed(cause)
- cont.resumeWithException(e)
- }
- }
-
- override fun toString(): String = "SimSuspendingResourceConsumer"
- })
-
- cont.invokeOnCancellation { cancel() }
- }
-}
diff --git a/opendc-simulator/opendc-simulator-resources/src/main/kotlin/org/opendc/simulator/resources/SimResourceScheduler.kt b/opendc-simulator/opendc-simulator-resources/src/main/kotlin/org/opendc/simulator/resources/SimResourceScheduler.kt
deleted file mode 100644
index a228c47b..00000000
--- a/opendc-simulator/opendc-simulator-resources/src/main/kotlin/org/opendc/simulator/resources/SimResourceScheduler.kt
+++ /dev/null
@@ -1,69 +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.resources
-
-import java.time.Clock
-
-/**
- * A resource scheduler is responsible for scheduling the communication and synchronization between multiple resource
- * providers and consumers.
- *
- * By centralizing the scheduling logic, updates of resources within a single system can be scheduled and tracked more
- * efficiently, reducing the overall work needed per update.
- */
-public interface SimResourceScheduler {
- /**
- * The [Clock] associated with this scheduler.
- */
- public val clock: Clock
-
- /**
- * Schedule a direct interrupt for the resource context represented by [flushable].
- *
- * @param flushable The resource context that needs to be flushed.
- * @param isIntermediate A flag to indicate that the intermediate progress of the resource consumer should be
- * flushed, but without interrupting the resource consumer to submit a new command. If false, the resource consumer
- * will be asked to deliver a new command and is essentially interrupted.
- */
- public fun schedule(flushable: SimResourceFlushable, isIntermediate: Boolean = false)
-
- /**
- * Schedule an interrupt in the future for the resource context represented by [flushable].
- *
- * This method will override earlier calls to this method for the same [flushable].
- *
- * @param flushable The resource context that needs to be flushed.
- * @param timestamp The timestamp when the interrupt should happen.
- * @param isIntermediate A flag to indicate that the intermediate progress of the resource consumer should be
- * flushed, but without interrupting the resource consumer to submit a new command. If false, the resource consumer
- * will be asked to deliver a new command and is essentially interrupted.
- */
- public fun schedule(flushable: SimResourceFlushable, timestamp: Long, isIntermediate: Boolean = false)
-
- /**
- * Batch the execution of several interrupts into a single call.
- *
- * This method is useful if you want to propagate the start of multiple resources (e.g., CPUs) in a single update.
- */
- public fun batch(block: () -> Unit)
-}
diff --git a/opendc-simulator/opendc-simulator-resources/src/main/kotlin/org/opendc/simulator/resources/SimResourceSchedulerTrampoline.kt b/opendc-simulator/opendc-simulator-resources/src/main/kotlin/org/opendc/simulator/resources/SimResourceSchedulerTrampoline.kt
deleted file mode 100644
index cdbb4a6c..00000000
--- a/opendc-simulator/opendc-simulator-resources/src/main/kotlin/org/opendc/simulator/resources/SimResourceSchedulerTrampoline.kt
+++ /dev/null
@@ -1,95 +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.resources
-
-import org.opendc.utils.TimerScheduler
-import java.time.Clock
-import java.util.ArrayDeque
-import kotlin.coroutines.CoroutineContext
-
-/**
- * A [SimResourceScheduler] queues all interrupts that occur during execution to be executed after.
- *
- * @param clock The virtual simulation clock.
- */
-public class SimResourceSchedulerTrampoline(context: CoroutineContext, override val clock: Clock) : SimResourceScheduler {
- /**
- * The [TimerScheduler] to actually schedule the interrupts.
- */
- private val timers = TimerScheduler<Any>(context, clock)
-
- /**
- * A flag to indicate that an interrupt is currently running already.
- */
- private var isRunning: Boolean = false
-
- /**
- * The queue of resources to be flushed.
- */
- private val queue = ArrayDeque<Pair<SimResourceFlushable, Boolean>>()
-
- override fun schedule(flushable: SimResourceFlushable, isIntermediate: Boolean) {
- queue.add(flushable to isIntermediate)
-
- if (isRunning) {
- return
- }
-
- flush()
- }
-
- override fun schedule(flushable: SimResourceFlushable, timestamp: Long, isIntermediate: Boolean) {
- timers.startSingleTimerTo(flushable, timestamp) {
- schedule(flushable, isIntermediate)
- }
- }
-
- override fun batch(block: () -> Unit) {
- val wasAlreadyRunning = isRunning
- try {
- isRunning = true
- block()
- } finally {
- if (!wasAlreadyRunning) {
- isRunning = false
- }
- }
- }
-
- /**
- * Flush the scheduled queue.
- */
- private fun flush() {
- val visited = mutableSetOf<SimResourceFlushable>()
- try {
- isRunning = true
- while (queue.isNotEmpty()) {
- val (flushable, isIntermediate) = queue.poll()
- flushable.flush(isIntermediate)
- visited.add(flushable)
- }
- } finally {
- isRunning = false
- }
- }
-}
diff --git a/opendc-simulator/opendc-simulator-resources/src/main/kotlin/org/opendc/simulator/resources/SimResourceSource.kt b/opendc-simulator/opendc-simulator-resources/src/main/kotlin/org/opendc/simulator/resources/SimResourceSource.kt
deleted file mode 100644
index 3277b889..00000000
--- a/opendc-simulator/opendc-simulator-resources/src/main/kotlin/org/opendc/simulator/resources/SimResourceSource.kt
+++ /dev/null
@@ -1,127 +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.resources
-
-import kotlin.math.ceil
-import kotlin.math.min
-
-/**
- * A [SimResourceSource] represents a source for some resource of type [R] that provides bounded processing capacity.
- *
- * @param initialCapacity The initial capacity of the resource.
- * @param scheduler The scheduler to schedule the interrupts.
- */
-public class SimResourceSource(
- initialCapacity: Double,
- private val scheduler: SimResourceScheduler
-) : SimResourceProvider {
- /**
- * The current processing speed of the resource.
- */
- public val speed: Double
- get() = ctx?.speed ?: 0.0
-
- /**
- * The capacity of the resource.
- */
- public var capacity: Double = initialCapacity
- set(value) {
- field = value
- ctx?.capacity = value
- }
-
- /**
- * The [Context] that is currently running.
- */
- private var ctx: Context? = null
-
- override var state: SimResourceState = SimResourceState.Pending
- private set
-
- override fun startConsumer(consumer: SimResourceConsumer) {
- check(state == SimResourceState.Pending) { "Resource is in invalid state" }
- val ctx = Context(consumer)
-
- this.ctx = ctx
- this.state = SimResourceState.Active
-
- ctx.start()
- }
-
- override fun close() {
- cancel()
- state = SimResourceState.Stopped
- }
-
- override fun interrupt() {
- ctx?.interrupt()
- }
-
- override fun cancel() {
- val ctx = ctx
- if (ctx != null) {
- this.ctx = null
- ctx.stop()
- }
-
- if (state != SimResourceState.Stopped) {
- state = SimResourceState.Pending
- }
- }
-
- /**
- * Internal implementation of [SimResourceContext] for this class.
- */
- private inner class Context(consumer: SimResourceConsumer) : SimAbstractResourceContext(capacity, scheduler, consumer) {
- override fun onIdle(deadline: Long) {
- // Do not resume if deadline is "infinite"
- if (deadline != Long.MAX_VALUE) {
- scheduler.schedule(this, deadline)
- }
- }
-
- override fun onConsume(work: Double, limit: Double, deadline: Long) {
- val until = min(deadline, clock.millis() + getDuration(work, speed))
- scheduler.schedule(this, until)
- }
-
- override fun onFinish() {
- cancel()
-
- ctx = null
-
- if (this@SimResourceSource.state != SimResourceState.Stopped) {
- this@SimResourceSource.state = SimResourceState.Pending
- }
- }
-
- override fun toString(): String = "SimResourceSource.Context[capacity=$capacity]"
- }
-
- /**
- * Compute the duration that a resource consumption will take with the specified [speed].
- */
- private fun getDuration(work: Double, speed: Double): Long {
- return ceil(work / speed * 1000).toLong()
- }
-}
diff --git a/opendc-simulator/opendc-simulator-resources/src/main/kotlin/org/opendc/simulator/resources/SimResourceSwitchExclusive.kt b/opendc-simulator/opendc-simulator-resources/src/main/kotlin/org/opendc/simulator/resources/SimResourceSwitchExclusive.kt
deleted file mode 100644
index 1a9dd0bc..00000000
--- a/opendc-simulator/opendc-simulator-resources/src/main/kotlin/org/opendc/simulator/resources/SimResourceSwitchExclusive.kt
+++ /dev/null
@@ -1,98 +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.resources
-
-import java.util.ArrayDeque
-
-/**
- * A [SimResourceSwitch] implementation that allocates outputs to the inputs of the switch exclusively. This means that
- * a single output is directly connected to an input and that the switch can only support as much outputs as inputs.
- */
-public class SimResourceSwitchExclusive : SimResourceSwitch {
- /**
- * A flag to indicate that the switch is closed.
- */
- private var isClosed: Boolean = false
-
- private val _outputs = mutableSetOf<Provider>()
- override val outputs: Set<SimResourceProvider>
- get() = _outputs
-
- private val availableResources = ArrayDeque<SimResourceTransformer>()
-
- private val _inputs = mutableSetOf<SimResourceProvider>()
- override val inputs: Set<SimResourceProvider>
- get() = _inputs
-
- override fun addOutput(capacity: Double): SimResourceProvider {
- check(!isClosed) { "Switch has been closed" }
- check(availableResources.isNotEmpty()) { "No capacity to serve request" }
- val forwarder = availableResources.poll()
- val output = Provider(capacity, forwarder)
- _outputs += output
- return output
- }
-
- override fun addInput(input: SimResourceProvider) {
- check(!isClosed) { "Switch has been closed" }
-
- if (input in inputs) {
- return
- }
-
- val forwarder = SimResourceForwarder()
-
- _inputs += input
- availableResources += forwarder
-
- input.startConsumer(object : SimResourceConsumer by forwarder {
- override fun onEvent(ctx: SimResourceContext, event: SimResourceEvent) {
- if (event == SimResourceEvent.Exit) {
- // De-register the input after it has finished
- _inputs -= input
- }
-
- forwarder.onEvent(ctx, event)
- }
- })
- }
-
- override fun close() {
- isClosed = true
-
- // Cancel all upstream subscriptions
- _inputs.forEach(SimResourceProvider::cancel)
- }
-
- private inner class Provider(
- private val capacity: Double,
- private val forwarder: SimResourceTransformer
- ) : SimResourceProvider by forwarder {
- override fun close() {
- // We explicitly do not close the forwarder here in order to re-use it across output resources.
-
- _outputs -= this
- availableResources += forwarder
- }
- }
-}
diff --git a/opendc-simulator/opendc-simulator-resources/src/main/kotlin/org/opendc/simulator/resources/SimResourceSwitchMaxMin.kt b/opendc-simulator/opendc-simulator-resources/src/main/kotlin/org/opendc/simulator/resources/SimResourceSwitchMaxMin.kt
deleted file mode 100644
index 5dc1e68d..00000000
--- a/opendc-simulator/opendc-simulator-resources/src/main/kotlin/org/opendc/simulator/resources/SimResourceSwitchMaxMin.kt
+++ /dev/null
@@ -1,118 +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.resources
-
-import kotlinx.coroutines.*
-
-/**
- * A [SimResourceSwitch] implementation that switches resource consumptions over the available resources using max-min
- * fair sharing.
- */
-public class SimResourceSwitchMaxMin(
- scheduler: SimResourceScheduler,
- private val listener: Listener? = null
-) : SimResourceSwitch {
- private val _outputs = mutableSetOf<SimResourceProvider>()
- override val outputs: Set<SimResourceProvider>
- get() = _outputs
-
- private val _inputs = mutableSetOf<SimResourceProvider>()
- override val inputs: Set<SimResourceProvider>
- get() = _inputs
-
- /**
- * A flag to indicate that the switch was closed.
- */
- private var isClosed = false
-
- /**
- * The aggregator to aggregate the resources.
- */
- private val aggregator = SimResourceAggregatorMaxMin(scheduler)
-
- /**
- * The distributor to distribute the aggregated resources.
- */
- private val distributor = SimResourceDistributorMaxMin(
- aggregator.output, scheduler,
- object : SimResourceDistributorMaxMin.Listener {
- override fun onSliceFinish(
- switch: SimResourceDistributor,
- requestedWork: Long,
- grantedWork: Long,
- overcommittedWork: Long,
- interferedWork: Long,
- cpuUsage: Double,
- cpuDemand: Double
- ) {
- listener?.onSliceFinish(this@SimResourceSwitchMaxMin, requestedWork, grantedWork, overcommittedWork, interferedWork, cpuUsage, cpuDemand)
- }
- }
- )
-
- /**
- * Add an output to the switch represented by [resource].
- */
- override fun addOutput(capacity: Double): SimResourceProvider {
- check(!isClosed) { "Switch has been closed" }
-
- val provider = distributor.addOutput(capacity)
- _outputs.add(provider)
- return provider
- }
-
- /**
- * Add the specified [input] to the switch.
- */
- override fun addInput(input: SimResourceProvider) {
- check(!isClosed) { "Switch has been closed" }
-
- aggregator.addInput(input)
- }
-
- override fun close() {
- if (!isClosed) {
- isClosed = true
- distributor.close()
- aggregator.close()
- }
- }
-
- /**
- * Event listener for hypervisor events.
- */
- public interface Listener {
- /**
- * This method is invoked when a slice is finished.
- */
- public fun onSliceFinish(
- switch: SimResourceSwitchMaxMin,
- requestedWork: Long,
- grantedWork: Long,
- overcommittedWork: Long,
- interferedWork: Long,
- cpuUsage: Double,
- cpuDemand: Double
- )
- }
-}
diff --git a/opendc-simulator/opendc-simulator-resources/src/main/kotlin/org/opendc/simulator/resources/SimResourceTransformer.kt b/opendc-simulator/opendc-simulator-resources/src/main/kotlin/org/opendc/simulator/resources/SimResourceTransformer.kt
deleted file mode 100644
index 32f3f573..00000000
--- a/opendc-simulator/opendc-simulator-resources/src/main/kotlin/org/opendc/simulator/resources/SimResourceTransformer.kt
+++ /dev/null
@@ -1,181 +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.resources
-
-/**
- * A [SimResourceFlow] that transforms the resource commands emitted by the resource commands to the resource provider.
- *
- * @param isCoupled A flag to indicate that the transformer will exit when the resource consumer exits.
- * @param transform The function to transform the received resource command.
- */
-public class SimResourceTransformer(
- private val isCoupled: Boolean = false,
- private val transform: (SimResourceContext, SimResourceCommand) -> SimResourceCommand
-) : SimResourceFlow {
- /**
- * The [SimResourceContext] in which the forwarder runs.
- */
- private var ctx: SimResourceContext? = null
-
- /**
- * The delegate [SimResourceConsumer].
- */
- private var delegate: SimResourceConsumer? = null
-
- /**
- * A flag to indicate that the delegate was started.
- */
- private var hasDelegateStarted: Boolean = false
-
- /**
- * The state of the forwarder.
- */
- override var state: SimResourceState = SimResourceState.Pending
- private set
-
- override fun startConsumer(consumer: SimResourceConsumer) {
- check(state == SimResourceState.Pending) { "Resource is in invalid state" }
-
- state = SimResourceState.Active
- delegate = consumer
-
- // Interrupt the provider to replace the consumer
- interrupt()
- }
-
- override fun interrupt() {
- ctx?.interrupt()
- }
-
- override fun cancel() {
- val delegate = delegate
- val ctx = ctx
-
- state = SimResourceState.Pending
-
- if (delegate != null && ctx != null) {
- this.delegate = null
- delegate.onEvent(ctx, SimResourceEvent.Exit)
- }
- }
-
- override fun close() {
- val ctx = ctx
-
- state = SimResourceState.Stopped
-
- if (ctx != null) {
- this.ctx = null
- ctx.interrupt()
- }
- }
-
- override fun onNext(ctx: SimResourceContext): SimResourceCommand {
- val delegate = delegate
-
- if (!hasDelegateStarted) {
- start()
- }
-
- return if (state == SimResourceState.Stopped) {
- SimResourceCommand.Exit
- } else if (delegate != null) {
- val command = transform(ctx, delegate.onNext(ctx))
- if (command == SimResourceCommand.Exit) {
- // Warning: resumption of the continuation might change the entire state of the forwarder. Make sure we
- // reset beforehand the existing state and check whether it has been updated afterwards
- reset()
-
- delegate.onEvent(ctx, SimResourceEvent.Exit)
-
- if (isCoupled || state == SimResourceState.Stopped)
- SimResourceCommand.Exit
- else
- onNext(ctx)
- } else {
- command
- }
- } else {
- SimResourceCommand.Idle()
- }
- }
-
- override fun onEvent(ctx: SimResourceContext, event: SimResourceEvent) {
- when (event) {
- SimResourceEvent.Start -> {
- this.ctx = ctx
- }
- SimResourceEvent.Exit -> {
- this.ctx = null
-
- val delegate = delegate
- if (delegate != null) {
- reset()
- delegate.onEvent(ctx, SimResourceEvent.Exit)
- }
- }
- else -> delegate?.onEvent(ctx, event)
- }
- }
-
- override fun onFailure(ctx: SimResourceContext, cause: Throwable) {
- this.ctx = null
-
- val delegate = delegate
- if (delegate != null) {
- reset()
- delegate.onFailure(ctx, cause)
- }
- }
-
- /**
- * Start the delegate.
- */
- private fun start() {
- val delegate = delegate ?: return
- delegate.onEvent(checkNotNull(ctx), SimResourceEvent.Start)
-
- hasDelegateStarted = true
- }
-
- /**
- * Reset the delegate.
- */
- private fun reset() {
- delegate = null
- hasDelegateStarted = false
-
- if (state != SimResourceState.Stopped) {
- state = SimResourceState.Pending
- }
- }
-}
-
-/**
- * Constructs a [SimResourceTransformer] that forwards the received resource command with an identity transform.
- *
- * @param isCoupled A flag to indicate that the transformer will exit when the resource consumer exits.
- */
-public fun SimResourceForwarder(isCoupled: Boolean = false): SimResourceTransformer {
- return SimResourceTransformer(isCoupled) { _, command -> command }
-}
diff --git a/opendc-simulator/opendc-simulator-resources/src/main/kotlin/org/opendc/simulator/resources/consumer/SimSpeedConsumerAdapter.kt b/opendc-simulator/opendc-simulator-resources/src/main/kotlin/org/opendc/simulator/resources/consumer/SimSpeedConsumerAdapter.kt
deleted file mode 100644
index 4f4ebb14..00000000
--- a/opendc-simulator/opendc-simulator-resources/src/main/kotlin/org/opendc/simulator/resources/consumer/SimSpeedConsumerAdapter.kt
+++ /dev/null
@@ -1,83 +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.resources.consumer
-
-import org.opendc.simulator.resources.SimResourceCommand
-import org.opendc.simulator.resources.SimResourceConsumer
-import org.opendc.simulator.resources.SimResourceContext
-import org.opendc.simulator.resources.SimResourceEvent
-import kotlin.math.min
-
-/**
- * Helper class to expose an observable [speed] field describing the speed of the consumer.
- */
-public class SimSpeedConsumerAdapter(
- private val delegate: SimResourceConsumer,
- private val callback: (Double) -> Unit = {}
-) : SimResourceConsumer by delegate {
- /**
- * The resource processing speed at this instant.
- */
- public var speed: Double = 0.0
- private set(value) {
- if (field != value) {
- callback(value)
- field = value
- }
- }
-
- init {
- callback(0.0)
- }
-
- override fun onNext(ctx: SimResourceContext): SimResourceCommand {
- return delegate.onNext(ctx)
- }
-
- override fun onEvent(ctx: SimResourceContext, event: SimResourceEvent) {
- val oldSpeed = speed
-
- delegate.onEvent(ctx, event)
-
- when (event) {
- SimResourceEvent.Run -> speed = ctx.speed
- SimResourceEvent.Capacity -> {
- // Check if the consumer interrupted the consumer and updated the resource consumption. If not, we might
- // need to update the current speed.
- if (oldSpeed == speed) {
- speed = min(ctx.capacity, speed)
- }
- }
- SimResourceEvent.Exit -> speed = 0.0
- else -> {}
- }
- }
-
- override fun onFailure(ctx: SimResourceContext, cause: Throwable) {
- speed = 0.0
-
- delegate.onFailure(ctx, cause)
- }
-
- override fun toString(): String = "SimSpeedConsumerAdapter[delegate=$delegate]"
-}
diff --git a/opendc-simulator/opendc-simulator-resources/src/main/kotlin/org/opendc/simulator/resources/consumer/SimTraceConsumer.kt b/opendc-simulator/opendc-simulator-resources/src/main/kotlin/org/opendc/simulator/resources/consumer/SimTraceConsumer.kt
deleted file mode 100644
index 2e94e1c1..00000000
--- a/opendc-simulator/opendc-simulator-resources/src/main/kotlin/org/opendc/simulator/resources/consumer/SimTraceConsumer.kt
+++ /dev/null
@@ -1,73 +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.resources.consumer
-
-import org.opendc.simulator.resources.SimResourceCommand
-import org.opendc.simulator.resources.SimResourceConsumer
-import org.opendc.simulator.resources.SimResourceContext
-import org.opendc.simulator.resources.SimResourceEvent
-
-/**
- * A [SimResourceConsumer] that replays a workload trace consisting of multiple fragments, each indicating the resource
- * consumption for some period of time.
- */
-public class SimTraceConsumer(private val trace: Sequence<Fragment>) : SimResourceConsumer {
- private var iterator: Iterator<Fragment>? = null
-
- override fun onNext(ctx: SimResourceContext): SimResourceCommand {
- val iterator = checkNotNull(iterator)
- return if (iterator.hasNext()) {
- val now = ctx.clock.millis()
- val fragment = iterator.next()
- val work = (fragment.duration / 1000) * fragment.usage
- val deadline = now + fragment.duration
-
- assert(deadline >= now) { "Deadline already passed" }
-
- if (work > 0.0)
- SimResourceCommand.Consume(work, fragment.usage, deadline)
- else
- SimResourceCommand.Idle(deadline)
- } else {
- SimResourceCommand.Exit
- }
- }
-
- override fun onEvent(ctx: SimResourceContext, event: SimResourceEvent) {
- when (event) {
- SimResourceEvent.Start -> {
- check(iterator == null) { "Consumer already running" }
- iterator = trace.iterator()
- }
- SimResourceEvent.Exit -> {
- iterator = null
- }
- else -> {}
- }
- }
-
- /**
- * A fragment of the workload.
- */
- public data class Fragment(val duration: Long, val usage: Double)
-}
diff --git a/opendc-simulator/opendc-simulator-resources/src/test/kotlin/org/opendc/simulator/resources/SimResourceAggregatorMaxMinTest.kt b/opendc-simulator/opendc-simulator-resources/src/test/kotlin/org/opendc/simulator/resources/SimResourceAggregatorMaxMinTest.kt
deleted file mode 100644
index 2b32300e..00000000
--- a/opendc-simulator/opendc-simulator-resources/src/test/kotlin/org/opendc/simulator/resources/SimResourceAggregatorMaxMinTest.kt
+++ /dev/null
@@ -1,201 +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.resources
-
-import io.mockk.every
-import io.mockk.mockk
-import io.mockk.verify
-import kotlinx.coroutines.*
-import org.junit.jupiter.api.Assertions.assertEquals
-import org.junit.jupiter.api.Test
-import org.junit.jupiter.api.assertAll
-import org.junit.jupiter.api.assertThrows
-import org.opendc.simulator.core.runBlockingSimulation
-import org.opendc.simulator.resources.consumer.SimSpeedConsumerAdapter
-import org.opendc.simulator.resources.consumer.SimWorkConsumer
-
-/**
- * Test suite for the [SimResourceAggregatorMaxMin] class.
- */
-@OptIn(ExperimentalCoroutinesApi::class)
-internal class SimResourceAggregatorMaxMinTest {
- @Test
- fun testSingleCapacity() = runBlockingSimulation {
- val scheduler = SimResourceSchedulerTrampoline(coroutineContext, clock)
-
- val aggregator = SimResourceAggregatorMaxMin(scheduler)
- val forwarder = SimResourceForwarder()
- val sources = listOf(
- forwarder,
- SimResourceSource(1.0, scheduler)
- )
- sources.forEach(aggregator::addInput)
-
- val consumer = SimWorkConsumer(1.0, 0.5)
- val usage = mutableListOf<Double>()
- val source = SimResourceSource(1.0, scheduler)
- val adapter = SimSpeedConsumerAdapter(forwarder, usage::add)
- source.startConsumer(adapter)
-
- try {
- aggregator.output.consume(consumer)
- yield()
-
- assertAll(
- { assertEquals(1000, clock.millis()) },
- { assertEquals(listOf(0.0, 0.5, 0.0), usage) }
- )
- } finally {
- aggregator.output.close()
- }
- }
-
- @Test
- fun testDoubleCapacity() = runBlockingSimulation {
- val scheduler = SimResourceSchedulerTrampoline(coroutineContext, clock)
-
- val aggregator = SimResourceAggregatorMaxMin(scheduler)
- val sources = listOf(
- SimResourceSource(1.0, scheduler),
- SimResourceSource(1.0, scheduler)
- )
- sources.forEach(aggregator::addInput)
-
- val consumer = SimWorkConsumer(2.0, 1.0)
- val usage = mutableListOf<Double>()
- val adapter = SimSpeedConsumerAdapter(consumer, usage::add)
-
- try {
- aggregator.output.consume(adapter)
- yield()
- assertAll(
- { assertEquals(1000, clock.millis()) },
- { assertEquals(listOf(0.0, 2.0, 0.0), usage) }
- )
- } finally {
- aggregator.output.close()
- }
- }
-
- @Test
- fun testOvercommit() = runBlockingSimulation {
- val scheduler = SimResourceSchedulerTrampoline(coroutineContext, clock)
-
- val aggregator = SimResourceAggregatorMaxMin(scheduler)
- val sources = listOf(
- SimResourceSource(1.0, scheduler),
- SimResourceSource(1.0, scheduler)
- )
- sources.forEach(aggregator::addInput)
-
- val consumer = mockk<SimResourceConsumer>(relaxUnitFun = true)
- every { consumer.onNext(any()) }
- .returns(SimResourceCommand.Consume(4.0, 4.0, 1000))
- .andThen(SimResourceCommand.Exit)
-
- try {
- aggregator.output.consume(consumer)
- yield()
- assertEquals(1000, clock.millis())
-
- verify(exactly = 2) { consumer.onNext(any()) }
- } finally {
- aggregator.output.close()
- }
- }
-
- @Test
- fun testException() = runBlockingSimulation {
- val scheduler = SimResourceSchedulerTrampoline(coroutineContext, clock)
-
- val aggregator = SimResourceAggregatorMaxMin(scheduler)
- val sources = listOf(
- SimResourceSource(1.0, scheduler),
- SimResourceSource(1.0, scheduler)
- )
- sources.forEach(aggregator::addInput)
-
- val consumer = mockk<SimResourceConsumer>(relaxUnitFun = true)
- every { consumer.onNext(any()) }
- .returns(SimResourceCommand.Consume(1.0, 1.0))
- .andThenThrows(IllegalStateException("Test Exception"))
-
- try {
- assertThrows<IllegalStateException> { aggregator.output.consume(consumer) }
- yield()
- assertEquals(SimResourceState.Pending, sources[0].state)
- } finally {
- aggregator.output.close()
- }
- }
-
- @Test
- fun testAdjustCapacity() = runBlockingSimulation {
- val scheduler = SimResourceSchedulerTrampoline(coroutineContext, clock)
-
- val aggregator = SimResourceAggregatorMaxMin(scheduler)
- val sources = listOf(
- SimResourceSource(1.0, scheduler),
- SimResourceSource(1.0, scheduler)
- )
- sources.forEach(aggregator::addInput)
-
- val consumer = SimWorkConsumer(4.0, 1.0)
- try {
- coroutineScope {
- launch { aggregator.output.consume(consumer) }
- delay(1000)
- sources[0].capacity = 0.5
- }
- yield()
- assertEquals(2334, clock.millis())
- } finally {
- aggregator.output.close()
- }
- }
-
- @Test
- fun testFailOverCapacity() = runBlockingSimulation {
- val scheduler = SimResourceSchedulerTrampoline(coroutineContext, clock)
-
- val aggregator = SimResourceAggregatorMaxMin(scheduler)
- val sources = listOf(
- SimResourceSource(1.0, scheduler),
- SimResourceSource(1.0, scheduler)
- )
- sources.forEach(aggregator::addInput)
-
- val consumer = SimWorkConsumer(1.0, 0.5)
- try {
- coroutineScope {
- launch { aggregator.output.consume(consumer) }
- delay(500)
- sources[0].capacity = 0.5
- }
- yield()
- assertEquals(1000, clock.millis())
- } finally {
- aggregator.output.close()
- }
- }
-}
diff --git a/opendc-simulator/opendc-simulator-resources/src/test/kotlin/org/opendc/simulator/resources/SimResourceCommandTest.kt b/opendc-simulator/opendc-simulator-resources/src/test/kotlin/org/opendc/simulator/resources/SimResourceCommandTest.kt
deleted file mode 100644
index 02d456ff..00000000
--- a/opendc-simulator/opendc-simulator-resources/src/test/kotlin/org/opendc/simulator/resources/SimResourceCommandTest.kt
+++ /dev/null
@@ -1,74 +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.resources
-
-import org.junit.jupiter.api.Test
-import org.junit.jupiter.api.assertDoesNotThrow
-import org.junit.jupiter.api.assertThrows
-
-/**
- * Test suite for [SimResourceCommand].
- */
-class SimResourceCommandTest {
- @Test
- fun testZeroWork() {
- assertThrows<IllegalArgumentException> {
- SimResourceCommand.Consume(0.0, 1.0)
- }
- }
-
- @Test
- fun testNegativeWork() {
- assertThrows<IllegalArgumentException> {
- SimResourceCommand.Consume(-1.0, 1.0)
- }
- }
-
- @Test
- fun testZeroLimit() {
- assertThrows<IllegalArgumentException> {
- SimResourceCommand.Consume(1.0, 0.0)
- }
- }
-
- @Test
- fun testNegativeLimit() {
- assertThrows<IllegalArgumentException> {
- SimResourceCommand.Consume(1.0, -1.0, 1)
- }
- }
-
- @Test
- fun testConsumeCorrect() {
- assertDoesNotThrow {
- SimResourceCommand.Consume(1.0, 1.0)
- }
- }
-
- @Test
- fun testIdleCorrect() {
- assertDoesNotThrow {
- SimResourceCommand.Idle(1)
- }
- }
-}
diff --git a/opendc-simulator/opendc-simulator-resources/src/test/kotlin/org/opendc/simulator/resources/SimResourceContextTest.kt b/opendc-simulator/opendc-simulator-resources/src/test/kotlin/org/opendc/simulator/resources/SimResourceContextTest.kt
deleted file mode 100644
index 2e2d6588..00000000
--- a/opendc-simulator/opendc-simulator-resources/src/test/kotlin/org/opendc/simulator/resources/SimResourceContextTest.kt
+++ /dev/null
@@ -1,147 +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.resources
-
-import io.mockk.*
-import kotlinx.coroutines.*
-import org.junit.jupiter.api.*
-import org.opendc.simulator.core.runBlockingSimulation
-
-/**
- * A test suite for the [SimAbstractResourceContext] class.
- */
-@OptIn(ExperimentalCoroutinesApi::class)
-class SimResourceContextTest {
- @Test
- fun testFlushWithoutCommand() = runBlockingSimulation {
- val scheduler = SimResourceSchedulerTrampoline(coroutineContext, clock)
- val consumer = mockk<SimResourceConsumer>(relaxUnitFun = true)
- every { consumer.onNext(any()) } returns SimResourceCommand.Consume(10.0, 1.0) andThen SimResourceCommand.Exit
-
- val context = object : SimAbstractResourceContext(4200.0, scheduler, consumer) {
- override fun onIdle(deadline: Long) {}
- override fun onConsume(work: Double, limit: Double, deadline: Long) {}
- override fun onFinish() {}
- }
-
- context.flush(isIntermediate = false)
- }
-
- @Test
- fun testIntermediateFlush() = runBlockingSimulation {
- val scheduler = SimResourceSchedulerTrampoline(coroutineContext, clock)
- val consumer = mockk<SimResourceConsumer>(relaxUnitFun = true)
- every { consumer.onNext(any()) } returns SimResourceCommand.Consume(10.0, 1.0) andThen SimResourceCommand.Exit
-
- val context = spyk(object : SimAbstractResourceContext(4200.0, scheduler, consumer) {
- override fun onIdle(deadline: Long) {}
- override fun onFinish() {}
- override fun onConsume(work: Double, limit: Double, deadline: Long) {}
- })
-
- context.start()
- delay(1) // Delay 1 ms to prevent hitting the fast path
- context.flush(isIntermediate = true)
-
- verify(exactly = 2) { context.onConsume(any(), any(), any()) }
- }
-
- @Test
- fun testIntermediateFlushIdle() = runBlockingSimulation {
- val scheduler = SimResourceSchedulerTrampoline(coroutineContext, clock)
- val consumer = mockk<SimResourceConsumer>(relaxUnitFun = true)
- every { consumer.onNext(any()) } returns SimResourceCommand.Idle(10) andThen SimResourceCommand.Exit
-
- val context = spyk(object : SimAbstractResourceContext(4200.0, scheduler, consumer) {
- override fun onIdle(deadline: Long) {}
- override fun onFinish() {}
- override fun onConsume(work: Double, limit: Double, deadline: Long) {}
- })
-
- context.start()
- delay(5)
- context.flush(isIntermediate = true)
- delay(5)
- context.flush(isIntermediate = true)
-
- assertAll(
- { verify(exactly = 2) { context.onIdle(any()) } },
- { verify(exactly = 1) { context.onFinish() } }
- )
- }
-
- @Test
- fun testDoubleStart() = runBlockingSimulation {
- val scheduler = SimResourceSchedulerTrampoline(coroutineContext, clock)
- val consumer = mockk<SimResourceConsumer>(relaxUnitFun = true)
- every { consumer.onNext(any()) } returns SimResourceCommand.Idle(10) andThen SimResourceCommand.Exit
-
- val context = object : SimAbstractResourceContext(4200.0, scheduler, consumer) {
- override fun onIdle(deadline: Long) {}
- override fun onFinish() {}
- override fun onConsume(work: Double, limit: Double, deadline: Long) {}
- }
-
- context.start()
- assertThrows<IllegalStateException> { context.start() }
- }
-
- @Test
- fun testIdempodentCapacityChange() = runBlockingSimulation {
- val scheduler = SimResourceSchedulerTrampoline(coroutineContext, clock)
- val consumer = mockk<SimResourceConsumer>(relaxUnitFun = true)
- every { consumer.onNext(any()) } returns SimResourceCommand.Consume(10.0, 1.0) andThen SimResourceCommand.Exit
-
- val context = object : SimAbstractResourceContext(4200.0, scheduler, consumer) {
- override fun onIdle(deadline: Long) {}
- override fun onConsume(work: Double, limit: Double, deadline: Long) {}
- override fun onFinish() {}
- }
-
- context.start()
- context.capacity = 4200.0
-
- verify(exactly = 0) { consumer.onEvent(any(), SimResourceEvent.Capacity) }
- }
-
- @Test
- fun testFailureNoInfiniteLoop() = runBlockingSimulation {
- val scheduler = SimResourceSchedulerTrampoline(coroutineContext, clock)
- val consumer = mockk<SimResourceConsumer>(relaxUnitFun = true)
- every { consumer.onNext(any()) } returns SimResourceCommand.Exit
- every { consumer.onEvent(any(), SimResourceEvent.Exit) } throws IllegalStateException("onEvent")
- every { consumer.onFailure(any(), any()) } throws IllegalStateException("onFailure")
-
- val context = object : SimAbstractResourceContext(4200.0, scheduler, consumer) {
- override fun onIdle(deadline: Long) {}
- override fun onConsume(work: Double, limit: Double, deadline: Long) {}
- override fun onFinish() {}
- }
-
- context.start()
-
- delay(1)
-
- verify(exactly = 1) { consumer.onFailure(any(), any()) }
- }
-}
diff --git a/opendc-simulator/opendc-simulator-resources/src/test/kotlin/org/opendc/simulator/resources/SimResourceSourceTest.kt b/opendc-simulator/opendc-simulator-resources/src/test/kotlin/org/opendc/simulator/resources/SimResourceSourceTest.kt
deleted file mode 100644
index 5e86088d..00000000
--- a/opendc-simulator/opendc-simulator-resources/src/test/kotlin/org/opendc/simulator/resources/SimResourceSourceTest.kt
+++ /dev/null
@@ -1,341 +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.resources
-
-import io.mockk.every
-import io.mockk.mockk
-import io.mockk.spyk
-import io.mockk.verify
-import kotlinx.coroutines.*
-import org.junit.jupiter.api.*
-import org.junit.jupiter.api.Assertions.assertEquals
-import org.opendc.simulator.core.runBlockingSimulation
-import org.opendc.simulator.resources.consumer.SimSpeedConsumerAdapter
-import org.opendc.simulator.resources.consumer.SimWorkConsumer
-
-/**
- * A test suite for the [SimResourceSource] class.
- */
-@OptIn(ExperimentalCoroutinesApi::class)
-class SimResourceSourceTest {
- @Test
- fun testSpeed() = runBlockingSimulation {
- val scheduler = SimResourceSchedulerTrampoline(coroutineContext, clock)
- val capacity = 4200.0
- val provider = SimResourceSource(capacity, scheduler)
-
- val consumer = mockk<SimResourceConsumer>(relaxUnitFun = true)
- every { consumer.onNext(any()) }
- .returns(SimResourceCommand.Consume(1000 * capacity, capacity))
- .andThen(SimResourceCommand.Exit)
-
- try {
- val res = mutableListOf<Double>()
- val adapter = SimSpeedConsumerAdapter(consumer, res::add)
-
- provider.consume(adapter)
-
- assertEquals(listOf(0.0, capacity, 0.0), res) { "Speed is reported correctly" }
- } finally {
- provider.close()
- }
- }
-
- @Test
- fun testAdjustCapacity() = runBlockingSimulation {
- val scheduler = SimResourceSchedulerTrampoline(coroutineContext, clock)
- val provider = SimResourceSource(1.0, scheduler)
-
- val consumer = spyk(SimWorkConsumer(2.0, 1.0))
-
- try {
- coroutineScope {
- launch { provider.consume(consumer) }
- delay(1000)
- provider.capacity = 0.5
- }
- assertEquals(3000, clock.millis())
- verify(exactly = 1) { consumer.onEvent(any(), SimResourceEvent.Capacity) }
- } finally {
- provider.close()
- }
- }
-
- @Test
- fun testSpeedLimit() = runBlockingSimulation {
- val scheduler = SimResourceSchedulerTrampoline(coroutineContext, clock)
- val capacity = 4200.0
- val provider = SimResourceSource(capacity, scheduler)
-
- val consumer = mockk<SimResourceConsumer>(relaxUnitFun = true)
- every { consumer.onNext(any()) }
- .returns(SimResourceCommand.Consume(1000 * capacity, 2 * capacity))
- .andThen(SimResourceCommand.Exit)
-
- try {
- val res = mutableListOf<Double>()
- val adapter = SimSpeedConsumerAdapter(consumer, res::add)
-
- provider.consume(adapter)
-
- assertEquals(listOf(0.0, capacity, 0.0), res) { "Speed is reported correctly" }
- } finally {
- provider.close()
- }
- }
-
- /**
- * Test to see whether no infinite recursion occurs when interrupting during [SimResourceConsumer.onStart] or
- * [SimResourceConsumer.onNext].
- */
- @Test
- fun testIntermediateInterrupt() = runBlockingSimulation {
- val scheduler = SimResourceSchedulerTrampoline(coroutineContext, clock)
- val capacity = 4200.0
- val provider = SimResourceSource(capacity, scheduler)
-
- val consumer = object : SimResourceConsumer {
- override fun onNext(ctx: SimResourceContext): SimResourceCommand {
- return SimResourceCommand.Exit
- }
-
- override fun onEvent(ctx: SimResourceContext, event: SimResourceEvent) {
- ctx.interrupt()
- }
- }
-
- try {
- provider.consume(consumer)
- } finally {
- provider.close()
- }
- }
-
- @Test
- fun testInterrupt() = runBlockingSimulation {
- val scheduler = SimResourceSchedulerTrampoline(coroutineContext, clock)
- val capacity = 4200.0
- val provider = SimResourceSource(capacity, scheduler)
- lateinit var resCtx: SimResourceContext
-
- val consumer = object : SimResourceConsumer {
- var isFirst = true
-
- override fun onEvent(ctx: SimResourceContext, event: SimResourceEvent) {
- when (event) {
- SimResourceEvent.Start -> resCtx = ctx
- else -> {}
- }
- }
-
- override fun onNext(ctx: SimResourceContext): SimResourceCommand {
- assertEquals(0.0, ctx.remainingWork)
- return if (isFirst) {
- isFirst = false
- SimResourceCommand.Consume(4.0, 1.0)
- } else {
- SimResourceCommand.Exit
- }
- }
- }
-
- try {
- launch {
- yield()
- resCtx.interrupt()
- }
- provider.consume(consumer)
-
- assertEquals(0, clock.millis())
- } finally {
- provider.close()
- }
- }
-
- @Test
- fun testFailure() = runBlockingSimulation {
- val scheduler = SimResourceSchedulerTrampoline(coroutineContext, clock)
- val capacity = 4200.0
- val provider = SimResourceSource(capacity, scheduler)
-
- val consumer = mockk<SimResourceConsumer>(relaxUnitFun = true)
- every { consumer.onEvent(any(), eq(SimResourceEvent.Start)) }
- .throws(IllegalStateException())
-
- try {
- assertThrows<IllegalStateException> {
- provider.consume(consumer)
- }
- } finally {
- provider.close()
- }
- }
-
- @Test
- fun testExceptionPropagationOnNext() = runBlockingSimulation {
- val scheduler = SimResourceSchedulerTrampoline(coroutineContext, clock)
- val capacity = 4200.0
- val provider = SimResourceSource(capacity, scheduler)
-
- val consumer = mockk<SimResourceConsumer>(relaxUnitFun = true)
- every { consumer.onNext(any()) }
- .returns(SimResourceCommand.Consume(1.0, 1.0))
- .andThenThrows(IllegalStateException())
-
- try {
- assertThrows<IllegalStateException> {
- provider.consume(consumer)
- }
- } finally {
- provider.close()
- }
- }
-
- @Test
- fun testConcurrentConsumption() = runBlockingSimulation {
- val scheduler = SimResourceSchedulerTrampoline(coroutineContext, clock)
- val capacity = 4200.0
- val provider = SimResourceSource(capacity, scheduler)
-
- val consumer = mockk<SimResourceConsumer>(relaxUnitFun = true)
- every { consumer.onNext(any()) }
- .returns(SimResourceCommand.Consume(1.0, 1.0))
- .andThenThrows(IllegalStateException())
-
- try {
- assertThrows<IllegalStateException> {
- coroutineScope {
- launch { provider.consume(consumer) }
- provider.consume(consumer)
- }
- }
- } finally {
- provider.close()
- }
- }
-
- @Test
- fun testClosedConsumption() = runBlockingSimulation {
- val scheduler = SimResourceSchedulerTrampoline(coroutineContext, clock)
- val capacity = 4200.0
- val provider = SimResourceSource(capacity, scheduler)
-
- val consumer = mockk<SimResourceConsumer>(relaxUnitFun = true)
- every { consumer.onNext(any()) }
- .returns(SimResourceCommand.Consume(1.0, 1.0))
- .andThenThrows(IllegalStateException())
-
- try {
- assertThrows<IllegalStateException> {
- provider.close()
- provider.consume(consumer)
- }
- } finally {
- provider.close()
- }
- }
-
- @Test
- fun testCloseDuringConsumption() = runBlockingSimulation {
- val scheduler = SimResourceSchedulerTrampoline(coroutineContext, clock)
- val capacity = 4200.0
- val provider = SimResourceSource(capacity, scheduler)
-
- val consumer = mockk<SimResourceConsumer>(relaxUnitFun = true)
- every { consumer.onNext(any()) }
- .returns(SimResourceCommand.Consume(1.0, 1.0))
- .andThenThrows(IllegalStateException())
-
- try {
- launch { provider.consume(consumer) }
- delay(500)
- provider.close()
-
- assertEquals(500, clock.millis())
- } finally {
- provider.close()
- }
- }
-
- @Test
- fun testIdle() = runBlockingSimulation {
- val scheduler = SimResourceSchedulerTrampoline(coroutineContext, clock)
- val capacity = 4200.0
- val provider = SimResourceSource(capacity, scheduler)
-
- val consumer = mockk<SimResourceConsumer>(relaxUnitFun = true)
- every { consumer.onNext(any()) }
- .returns(SimResourceCommand.Idle(clock.millis() + 500))
- .andThen(SimResourceCommand.Exit)
-
- try {
- provider.consume(consumer)
-
- assertEquals(500, clock.millis())
- } finally {
- provider.close()
- }
- }
-
- @Test
- fun testInfiniteSleep() {
- assertThrows<IllegalStateException> {
- runBlockingSimulation {
- val scheduler = SimResourceSchedulerTrampoline(coroutineContext, clock)
- val capacity = 4200.0
- val provider = SimResourceSource(capacity, scheduler)
-
- val consumer = mockk<SimResourceConsumer>(relaxUnitFun = true)
- every { consumer.onNext(any()) }
- .returns(SimResourceCommand.Idle())
- .andThenThrows(IllegalStateException())
-
- try {
- provider.consume(consumer)
- } finally {
- provider.close()
- }
- }
- }
- }
-
- @Test
- fun testIncorrectDeadline() = runBlockingSimulation {
- val scheduler = SimResourceSchedulerTrampoline(coroutineContext, clock)
- val capacity = 4200.0
- val provider = SimResourceSource(capacity, scheduler)
-
- val consumer = mockk<SimResourceConsumer>(relaxUnitFun = true)
- every { consumer.onNext(any()) }
- .returns(SimResourceCommand.Idle(2))
- .andThen(SimResourceCommand.Exit)
-
- try {
- delay(10)
-
- assertThrows<IllegalArgumentException> { provider.consume(consumer) }
- } finally {
- provider.close()
- }
- }
-}
diff --git a/opendc-simulator/opendc-simulator-resources/src/test/kotlin/org/opendc/simulator/resources/SimResourceSwitchExclusiveTest.kt b/opendc-simulator/opendc-simulator-resources/src/test/kotlin/org/opendc/simulator/resources/SimResourceSwitchExclusiveTest.kt
deleted file mode 100644
index 32b6d8ad..00000000
--- a/opendc-simulator/opendc-simulator-resources/src/test/kotlin/org/opendc/simulator/resources/SimResourceSwitchExclusiveTest.kt
+++ /dev/null
@@ -1,175 +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.resources
-
-import io.mockk.every
-import io.mockk.mockk
-import kotlinx.coroutines.ExperimentalCoroutinesApi
-import kotlinx.coroutines.yield
-import org.junit.jupiter.api.Assertions.assertEquals
-import org.junit.jupiter.api.Test
-import org.junit.jupiter.api.assertAll
-import org.junit.jupiter.api.assertThrows
-import org.opendc.simulator.core.runBlockingSimulation
-import org.opendc.simulator.resources.consumer.SimSpeedConsumerAdapter
-import org.opendc.simulator.resources.consumer.SimTraceConsumer
-
-/**
- * Test suite for the [SimResourceSwitchExclusive] class.
- */
-@OptIn(ExperimentalCoroutinesApi::class)
-internal class SimResourceSwitchExclusiveTest {
- /**
- * Test a trace workload.
- */
- @Test
- fun testTrace() = runBlockingSimulation {
- val scheduler = SimResourceSchedulerTrampoline(coroutineContext, clock)
-
- val speed = mutableListOf<Double>()
-
- val duration = 5 * 60L
- val workload =
- SimTraceConsumer(
- sequenceOf(
- SimTraceConsumer.Fragment(duration * 1000, 28.0),
- SimTraceConsumer.Fragment(duration * 1000, 3500.0),
- SimTraceConsumer.Fragment(duration * 1000, 0.0),
- SimTraceConsumer.Fragment(duration * 1000, 183.0)
- ),
- )
-
- val switch = SimResourceSwitchExclusive()
- val source = SimResourceSource(3200.0, scheduler)
- val forwarder = SimResourceForwarder()
- val adapter = SimSpeedConsumerAdapter(forwarder, speed::add)
- source.startConsumer(adapter)
- switch.addInput(forwarder)
-
- val provider = switch.addOutput(3200.0)
-
- try {
- provider.consume(workload)
- yield()
- } finally {
- provider.close()
- }
-
- assertAll(
- { assertEquals(listOf(0.0, 28.0, 3200.0, 0.0, 183.0, 0.0), speed) { "Correct speed" } },
- { assertEquals(5 * 60L * 4000, clock.millis()) { "Took enough time" } }
- )
- }
-
- /**
- * Test runtime workload on hypervisor.
- */
- @Test
- fun testRuntimeWorkload() = runBlockingSimulation {
- val scheduler = SimResourceSchedulerTrampoline(coroutineContext, clock)
-
- val duration = 5 * 60L * 1000
- val workload = mockk<SimResourceConsumer>(relaxUnitFun = true)
- every { workload.onNext(any()) } returns SimResourceCommand.Consume(duration / 1000.0, 1.0) andThen SimResourceCommand.Exit
-
- val switch = SimResourceSwitchExclusive()
- val source = SimResourceSource(3200.0, scheduler)
-
- switch.addInput(source)
-
- val provider = switch.addOutput(3200.0)
-
- try {
- provider.consume(workload)
- yield()
- } finally {
- provider.close()
- }
- assertEquals(duration, clock.millis()) { "Took enough time" }
- }
-
- /**
- * Test two workloads running sequentially.
- */
- @Test
- fun testTwoWorkloads() = runBlockingSimulation {
- val scheduler = SimResourceSchedulerTrampoline(coroutineContext, clock)
-
- val duration = 5 * 60L * 1000
- val workload = object : SimResourceConsumer {
- var isFirst = true
-
- override fun onEvent(ctx: SimResourceContext, event: SimResourceEvent) {
- when (event) {
- SimResourceEvent.Start -> isFirst = true
- else -> {}
- }
- }
-
- override fun onNext(ctx: SimResourceContext): SimResourceCommand {
- return if (isFirst) {
- isFirst = false
- SimResourceCommand.Consume(duration / 1000.0, 1.0)
- } else {
- SimResourceCommand.Exit
- }
- }
- }
-
- val switch = SimResourceSwitchExclusive()
- val source = SimResourceSource(3200.0, scheduler)
-
- switch.addInput(source)
-
- val provider = switch.addOutput(3200.0)
-
- try {
- provider.consume(workload)
- yield()
- provider.consume(workload)
- } finally {
- provider.close()
- }
- assertEquals(duration * 2, clock.millis()) { "Took enough time" }
- }
-
- /**
- * Test concurrent workloads on the machine.
- */
- @Test
- fun testConcurrentWorkloadFails() = runBlockingSimulation {
- val scheduler = SimResourceSchedulerTrampoline(coroutineContext, clock)
-
- val duration = 5 * 60L * 1000
- val workload = mockk<SimResourceConsumer>(relaxUnitFun = true)
- every { workload.onNext(any()) } returns SimResourceCommand.Consume(duration / 1000.0, 1.0) andThen SimResourceCommand.Exit
-
- val switch = SimResourceSwitchExclusive()
- val source = SimResourceSource(3200.0, scheduler)
-
- switch.addInput(source)
-
- switch.addOutput(3200.0)
- assertThrows<IllegalStateException> { switch.addOutput(3200.0) }
- }
-}
diff --git a/opendc-simulator/opendc-simulator-resources/src/test/kotlin/org/opendc/simulator/resources/SimResourceSwitchMaxMinTest.kt b/opendc-simulator/opendc-simulator-resources/src/test/kotlin/org/opendc/simulator/resources/SimResourceSwitchMaxMinTest.kt
deleted file mode 100644
index e7dec172..00000000
--- a/opendc-simulator/opendc-simulator-resources/src/test/kotlin/org/opendc/simulator/resources/SimResourceSwitchMaxMinTest.kt
+++ /dev/null
@@ -1,189 +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.resources
-
-import io.mockk.every
-import io.mockk.mockk
-import kotlinx.coroutines.ExperimentalCoroutinesApi
-import kotlinx.coroutines.coroutineScope
-import kotlinx.coroutines.launch
-import kotlinx.coroutines.yield
-import org.junit.jupiter.api.*
-import org.junit.jupiter.api.Assertions.assertEquals
-import org.opendc.simulator.core.runBlockingSimulation
-import org.opendc.simulator.resources.consumer.SimTraceConsumer
-
-/**
- * Test suite for the [SimResourceSwitch] implementations
- */
-@OptIn(ExperimentalCoroutinesApi::class)
-internal class SimResourceSwitchMaxMinTest {
- @Test
- fun testSmoke() = runBlockingSimulation {
- val scheduler = SimResourceSchedulerTrampoline(coroutineContext, clock)
- val switch = SimResourceSwitchMaxMin(scheduler)
-
- val sources = List(2) { SimResourceSource(2000.0, scheduler) }
- sources.forEach { switch.addInput(it) }
-
- val provider = switch.addOutput(1000.0)
-
- val consumer = mockk<SimResourceConsumer>(relaxUnitFun = true)
- every { consumer.onNext(any()) } returns SimResourceCommand.Consume(1.0, 1.0) andThen SimResourceCommand.Exit
-
- try {
- provider.consume(consumer)
- yield()
- } finally {
- switch.close()
- }
- }
-
- /**
- * Test overcommitting of resources via the hypervisor with a single VM.
- */
- @Test
- fun testOvercommittedSingle() = runBlockingSimulation {
- val scheduler = SimResourceSchedulerTrampoline(coroutineContext, clock)
-
- val listener = object : SimResourceSwitchMaxMin.Listener {
- var totalRequestedWork = 0L
- var totalGrantedWork = 0L
- var totalOvercommittedWork = 0L
-
- override fun onSliceFinish(
- switch: SimResourceSwitchMaxMin,
- requestedWork: Long,
- grantedWork: Long,
- overcommittedWork: Long,
- interferedWork: Long,
- cpuUsage: Double,
- cpuDemand: Double
- ) {
- totalRequestedWork += requestedWork
- totalGrantedWork += grantedWork
- totalOvercommittedWork += overcommittedWork
- }
- }
-
- val duration = 5 * 60L
- val workload =
- SimTraceConsumer(
- sequenceOf(
- SimTraceConsumer.Fragment(duration * 1000, 28.0),
- SimTraceConsumer.Fragment(duration * 1000, 3500.0),
- SimTraceConsumer.Fragment(duration * 1000, 0.0),
- SimTraceConsumer.Fragment(duration * 1000, 183.0)
- ),
- )
-
- val switch = SimResourceSwitchMaxMin(scheduler, listener)
- val provider = switch.addOutput(3200.0)
-
- try {
- switch.addInput(SimResourceSource(3200.0, scheduler))
- provider.consume(workload)
- yield()
- } finally {
- switch.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(1200000, clock.millis()) }
- )
- }
-
- /**
- * Test overcommitting of resources via the hypervisor with two VMs.
- */
- @Test
- fun testOvercommittedDual() = runBlockingSimulation {
- val scheduler = SimResourceSchedulerTrampoline(coroutineContext, clock)
-
- val listener = object : SimResourceSwitchMaxMin.Listener {
- var totalRequestedWork = 0L
- var totalGrantedWork = 0L
- var totalOvercommittedWork = 0L
-
- override fun onSliceFinish(
- switch: SimResourceSwitchMaxMin,
- requestedWork: Long,
- grantedWork: Long,
- overcommittedWork: Long,
- interferedWork: Long,
- cpuUsage: Double,
- cpuDemand: Double
- ) {
- totalRequestedWork += requestedWork
- totalGrantedWork += grantedWork
- totalOvercommittedWork += overcommittedWork
- }
- }
-
- val duration = 5 * 60L
- val workloadA =
- SimTraceConsumer(
- sequenceOf(
- SimTraceConsumer.Fragment(duration * 1000, 28.0),
- SimTraceConsumer.Fragment(duration * 1000, 3500.0),
- SimTraceConsumer.Fragment(duration * 1000, 0.0),
- SimTraceConsumer.Fragment(duration * 1000, 183.0)
- ),
- )
- val workloadB =
- SimTraceConsumer(
- sequenceOf(
- SimTraceConsumer.Fragment(duration * 1000, 28.0),
- SimTraceConsumer.Fragment(duration * 1000, 3100.0),
- SimTraceConsumer.Fragment(duration * 1000, 0.0),
- SimTraceConsumer.Fragment(duration * 1000, 73.0)
- )
- )
-
- val switch = SimResourceSwitchMaxMin(scheduler, listener)
- val providerA = switch.addOutput(3200.0)
- val providerB = switch.addOutput(3200.0)
-
- try {
- switch.addInput(SimResourceSource(3200.0, scheduler))
-
- coroutineScope {
- launch { providerA.consume(workloadA) }
- providerB.consume(workloadB)
- }
-
- yield()
- } finally {
- switch.close()
- }
- 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-resources/src/test/kotlin/org/opendc/simulator/resources/SimResourceTransformerTest.kt b/opendc-simulator/opendc-simulator-resources/src/test/kotlin/org/opendc/simulator/resources/SimResourceTransformerTest.kt
deleted file mode 100644
index 880e1755..00000000
--- a/opendc-simulator/opendc-simulator-resources/src/test/kotlin/org/opendc/simulator/resources/SimResourceTransformerTest.kt
+++ /dev/null
@@ -1,208 +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.resources
-
-import io.mockk.every
-import io.mockk.mockk
-import io.mockk.spyk
-import io.mockk.verify
-import kotlinx.coroutines.*
-import org.junit.jupiter.api.Assertions.*
-import org.junit.jupiter.api.Test
-import org.junit.jupiter.api.assertThrows
-import org.opendc.simulator.core.runBlockingSimulation
-import org.opendc.simulator.resources.consumer.SimWorkConsumer
-
-/**
- * A test suite for the [SimResourceTransformer] class.
- */
-@OptIn(ExperimentalCoroutinesApi::class)
-internal class SimResourceTransformerTest {
- @Test
- fun testExitImmediately() = runBlockingSimulation {
- val forwarder = SimResourceForwarder()
- val scheduler = SimResourceSchedulerTrampoline(coroutineContext, clock)
- val source = SimResourceSource(2000.0, scheduler)
-
- launch {
- source.consume(forwarder)
- source.close()
- }
-
- forwarder.consume(object : SimResourceConsumer {
- override fun onNext(ctx: SimResourceContext): SimResourceCommand {
- return SimResourceCommand.Exit
- }
- })
-
- forwarder.close()
- }
-
- @Test
- fun testExit() = runBlockingSimulation {
- val forwarder = SimResourceForwarder()
- val scheduler = SimResourceSchedulerTrampoline(coroutineContext, clock)
- val source = SimResourceSource(2000.0, scheduler)
-
- launch {
- source.consume(forwarder)
- source.close()
- }
-
- forwarder.consume(object : SimResourceConsumer {
- var isFirst = true
-
- override fun onNext(ctx: SimResourceContext): SimResourceCommand {
- return if (isFirst) {
- isFirst = false
- SimResourceCommand.Consume(10.0, 1.0)
- } else {
- SimResourceCommand.Exit
- }
- }
- })
-
- forwarder.close()
- }
-
- @Test
- fun testState() = runBlockingSimulation {
- val forwarder = SimResourceForwarder()
- val consumer = object : SimResourceConsumer {
- override fun onNext(ctx: SimResourceContext): SimResourceCommand = SimResourceCommand.Exit
- }
-
- assertEquals(SimResourceState.Pending, forwarder.state)
-
- forwarder.startConsumer(consumer)
- assertEquals(SimResourceState.Active, forwarder.state)
-
- assertThrows<IllegalStateException> { forwarder.startConsumer(consumer) }
-
- forwarder.cancel()
- assertEquals(SimResourceState.Pending, forwarder.state)
-
- forwarder.close()
- assertEquals(SimResourceState.Stopped, forwarder.state)
- }
-
- @Test
- fun testCancelPendingDelegate() = runBlockingSimulation {
- val forwarder = SimResourceForwarder()
-
- val consumer = mockk<SimResourceConsumer>(relaxUnitFun = true)
- every { consumer.onNext(any()) } returns SimResourceCommand.Exit
-
- forwarder.startConsumer(consumer)
- forwarder.cancel()
-
- verify(exactly = 0) { consumer.onEvent(any(), SimResourceEvent.Exit) }
- }
-
- @Test
- fun testCancelStartedDelegate() = runBlockingSimulation {
- val forwarder = SimResourceForwarder()
- val scheduler = SimResourceSchedulerTrampoline(coroutineContext, clock)
- val source = SimResourceSource(2000.0, scheduler)
-
- val consumer = mockk<SimResourceConsumer>(relaxUnitFun = true)
- every { consumer.onNext(any()) } returns SimResourceCommand.Idle(10)
-
- source.startConsumer(forwarder)
- yield()
- forwarder.startConsumer(consumer)
- yield()
- forwarder.cancel()
-
- verify(exactly = 1) { consumer.onEvent(any(), SimResourceEvent.Start) }
- verify(exactly = 1) { consumer.onEvent(any(), SimResourceEvent.Exit) }
- }
-
- @Test
- fun testCancelPropagation() = runBlockingSimulation {
- val forwarder = SimResourceForwarder()
- val scheduler = SimResourceSchedulerTrampoline(coroutineContext, clock)
- val source = SimResourceSource(2000.0, scheduler)
-
- val consumer = mockk<SimResourceConsumer>(relaxUnitFun = true)
- every { consumer.onNext(any()) } returns SimResourceCommand.Idle(10)
-
- source.startConsumer(forwarder)
- yield()
- forwarder.startConsumer(consumer)
- yield()
- source.cancel()
-
- verify(exactly = 1) { consumer.onEvent(any(), SimResourceEvent.Start) }
- verify(exactly = 1) { consumer.onEvent(any(), SimResourceEvent.Exit) }
- }
-
- @Test
- fun testExitPropagation() = runBlockingSimulation {
- val forwarder = SimResourceForwarder(isCoupled = true)
- val scheduler = SimResourceSchedulerTrampoline(coroutineContext, clock)
- val source = SimResourceSource(2000.0, scheduler)
-
- val consumer = mockk<SimResourceConsumer>(relaxUnitFun = true)
- every { consumer.onNext(any()) } returns SimResourceCommand.Exit
-
- source.startConsumer(forwarder)
- forwarder.consume(consumer)
- yield()
-
- assertEquals(SimResourceState.Pending, source.state)
- }
-
- @Test
- fun testAdjustCapacity() = runBlockingSimulation {
- val forwarder = SimResourceForwarder()
- val scheduler = SimResourceSchedulerTrampoline(coroutineContext, clock)
- val source = SimResourceSource(1.0, scheduler)
-
- val consumer = spyk(SimWorkConsumer(2.0, 1.0))
- source.startConsumer(forwarder)
-
- coroutineScope {
- launch { forwarder.consume(consumer) }
- delay(1000)
- source.capacity = 0.5
- }
-
- assertEquals(3000, clock.millis())
- verify(exactly = 1) { consumer.onEvent(any(), SimResourceEvent.Capacity) }
- }
-
- @Test
- fun testTransformExit() = runBlockingSimulation {
- val forwarder = SimResourceTransformer { _, _ -> SimResourceCommand.Exit }
- val scheduler = SimResourceSchedulerTrampoline(coroutineContext, clock)
- val source = SimResourceSource(1.0, scheduler)
-
- val consumer = spyk(SimWorkConsumer(2.0, 1.0))
- source.startConsumer(forwarder)
- forwarder.consume(consumer)
-
- assertEquals(0, clock.millis())
- verify(exactly = 1) { consumer.onNext(any()) }
- }
-}