summaryrefslogtreecommitdiff
path: root/opendc-simulator
diff options
context:
space:
mode:
authorFabian Mastenbroek <mail.fabianm@gmail.com>2022-10-21 22:32:05 +0200
committerGitHub <noreply@github.com>2022-10-21 22:32:05 +0200
commitfa7fdbb0126ea465130961dc37c4ef2d6feb36e9 (patch)
tree9cd46dd7970870b78990d6c35e8e2759d7cf5a13 /opendc-simulator
parent29beb50018cf2ad87b252c6c080f8c5de4600349 (diff)
parent290e1fe14460d91e4703e55ac5f05dbe7b4505f7 (diff)
merge: Implement multi-flow stages in simulator (#110)
This pull request introduces the new `flow2` multi-flow simulator into the OpenDC codebase and adjust all existing modules to make use of this new simulator. The new simulator models flow as a network of components, which can each receive flow from (potentially) multiple other components. In the previous simulator, the framework itself supported only single flows between components and required re-implementation of many components to support multiplexing flows. Initial benchmarks show performance improvements in the range 2x–4x for large scale experiments such as the Capelin benchmarks. ## Implementation Notes :hammer_and_pick: * Add support for multi-flow stages * Support flow transformations * Add forwarding flow multiplexer * Expose metrics on FlowMultiplexer * Re-implement network sim using flow2 * Re-implement power sim using flow2 * Re-implement compute sim using flow2 * Optimize workload implementation of SimTrace * Remove old flow simulator * Add log4j-core dependency ## External Dependencies :four_leaf_clover: * N/A ## Breaking API Changes :warning: * Removal of the `org.opendc.simulator.flow` package. You should now use the new flow simulator located in `org.opendc.simulator.flow2`. * `PowerModel` interface is replaced by the `CpuPowerModel` interface. * `PowerDriver` interface is replaced by the `SimPsu` and `SimPsuFactory` interfaces. * Removal of `SimTraceWorkload`. Instead, create a workload from a `SimTrace` using `createWorkload(offset)`. * `ScalingGovernor` has been split in a `ScalingGovernor` and `ScalingGovernorFactory`. * All modules in `opendc-simulator` are now written in Java. This means that default parameters are not supported anymore for these modules.
Diffstat (limited to 'opendc-simulator')
-rw-r--r--opendc-simulator/opendc-simulator-compute/src/jmh/kotlin/org/opendc/simulator/compute/SimMachineBenchmarks.kt53
-rw-r--r--opendc-simulator/opendc-simulator-compute/src/main/java/org/opendc/simulator/compute/SimAbstractMachine.java327
-rw-r--r--opendc-simulator/opendc-simulator-compute/src/main/java/org/opendc/simulator/compute/SimBareMetalMachine.java298
-rw-r--r--opendc-simulator/opendc-simulator-compute/src/main/java/org/opendc/simulator/compute/SimMachine.java (renamed from opendc-simulator/opendc-simulator-compute/src/main/kotlin/org/opendc/simulator/compute/SimMachine.kt)34
-rw-r--r--opendc-simulator/opendc-simulator-compute/src/main/java/org/opendc/simulator/compute/SimMachineContext.java74
-rw-r--r--opendc-simulator/opendc-simulator-compute/src/main/java/org/opendc/simulator/compute/SimMemory.java (renamed from opendc-simulator/opendc-simulator-compute/src/main/kotlin/org/opendc/simulator/compute/SimMemory.kt)20
-rw-r--r--opendc-simulator/opendc-simulator-compute/src/main/java/org/opendc/simulator/compute/SimNetworkInterface.java (renamed from opendc-simulator/opendc-simulator-compute/src/main/kotlin/org/opendc/simulator/compute/SimNetworkInterface.kt)24
-rw-r--r--opendc-simulator/opendc-simulator-compute/src/main/java/org/opendc/simulator/compute/SimProcessingUnit.java (renamed from opendc-simulator/opendc-simulator-flow/src/main/kotlin/org/opendc/simulator/flow/FlowConnection.kt)56
-rw-r--r--opendc-simulator/opendc-simulator-compute/src/main/java/org/opendc/simulator/compute/SimPsu.java71
-rw-r--r--opendc-simulator/opendc-simulator-compute/src/main/java/org/opendc/simulator/compute/SimPsuFactories.java214
-rw-r--r--opendc-simulator/opendc-simulator-compute/src/main/java/org/opendc/simulator/compute/SimPsuFactory.java (renamed from opendc-simulator/opendc-simulator-flow/src/main/kotlin/org/opendc/simulator/flow/FlowConvergenceListener.kt)17
-rw-r--r--opendc-simulator/opendc-simulator-compute/src/main/java/org/opendc/simulator/compute/SimStorageInterface.java (renamed from opendc-simulator/opendc-simulator-compute/src/main/kotlin/org/opendc/simulator/compute/SimStorageInterface.kt)22
-rw-r--r--opendc-simulator/opendc-simulator-compute/src/main/java/org/opendc/simulator/compute/device/SimNetworkAdapter.java (renamed from opendc-simulator/opendc-simulator-compute/src/main/kotlin/org/opendc/simulator/compute/device/SimNetworkAdapter.kt)16
-rw-r--r--opendc-simulator/opendc-simulator-compute/src/main/java/org/opendc/simulator/compute/device/SimPeripheral.java (renamed from opendc-simulator/opendc-simulator-compute/src/main/kotlin/org/opendc/simulator/compute/device/SimPeripheral.kt)12
-rw-r--r--opendc-simulator/opendc-simulator-compute/src/main/java/org/opendc/simulator/compute/kernel/SimHypervisor.java911
-rw-r--r--opendc-simulator/opendc-simulator-compute/src/main/java/org/opendc/simulator/compute/kernel/SimHypervisorCounters.java (renamed from opendc-simulator/opendc-simulator-compute/src/main/kotlin/org/opendc/simulator/compute/kernel/SimHypervisorCounters.kt)26
-rw-r--r--opendc-simulator/opendc-simulator-compute/src/main/java/org/opendc/simulator/compute/kernel/SimVirtualMachine.java (renamed from opendc-simulator/opendc-simulator-compute/src/main/kotlin/org/opendc/simulator/compute/kernel/SimVirtualMachine.kt)26
-rw-r--r--opendc-simulator/opendc-simulator-compute/src/main/java/org/opendc/simulator/compute/kernel/cpufreq/ScalingGovernor.java (renamed from opendc-simulator/opendc-simulator-compute/src/main/kotlin/org/opendc/simulator/compute/kernel/cpufreq/ScalingGovernor.kt)30
-rw-r--r--opendc-simulator/opendc-simulator-compute/src/main/java/org/opendc/simulator/compute/kernel/cpufreq/ScalingGovernorFactory.java33
-rw-r--r--opendc-simulator/opendc-simulator-compute/src/main/java/org/opendc/simulator/compute/kernel/cpufreq/ScalingGovernors.java190
-rw-r--r--opendc-simulator/opendc-simulator-compute/src/main/java/org/opendc/simulator/compute/kernel/cpufreq/ScalingPolicy.java (renamed from opendc-simulator/opendc-simulator-compute/src/main/kotlin/org/opendc/simulator/compute/kernel/cpufreq/ScalingPolicy.kt)29
-rw-r--r--opendc-simulator/opendc-simulator-compute/src/main/java/org/opendc/simulator/compute/kernel/interference/VmInterferenceDomain.java136
-rw-r--r--opendc-simulator/opendc-simulator-compute/src/main/java/org/opendc/simulator/compute/kernel/interference/VmInterferenceMember.java177
-rw-r--r--opendc-simulator/opendc-simulator-compute/src/main/java/org/opendc/simulator/compute/kernel/interference/VmInterferenceModel.java185
-rw-r--r--opendc-simulator/opendc-simulator-compute/src/main/java/org/opendc/simulator/compute/kernel/interference/VmInterferenceProfile.java (renamed from opendc-simulator/opendc-simulator-compute/src/main/kotlin/org/opendc/simulator/compute/kernel/interference/VmInterferenceProfile.kt)49
-rw-r--r--opendc-simulator/opendc-simulator-compute/src/main/java/org/opendc/simulator/compute/model/MachineModel.java149
-rw-r--r--opendc-simulator/opendc-simulator-compute/src/main/java/org/opendc/simulator/compute/model/MemoryUnit.java100
-rw-r--r--opendc-simulator/opendc-simulator-compute/src/main/java/org/opendc/simulator/compute/model/NetworkAdapter.java88
-rw-r--r--opendc-simulator/opendc-simulator-compute/src/main/java/org/opendc/simulator/compute/model/ProcessingNode.java100
-rw-r--r--opendc-simulator/opendc-simulator-compute/src/main/java/org/opendc/simulator/compute/model/ProcessingUnit.java86
-rw-r--r--opendc-simulator/opendc-simulator-compute/src/main/java/org/opendc/simulator/compute/model/StorageDevice.java112
-rw-r--r--opendc-simulator/opendc-simulator-compute/src/main/java/org/opendc/simulator/compute/power/CpuPowerModel.java (renamed from opendc-simulator/opendc-simulator-compute/src/main/kotlin/org/opendc/simulator/compute/power/PowerModel.kt)14
-rw-r--r--opendc-simulator/opendc-simulator-compute/src/main/java/org/opendc/simulator/compute/power/CpuPowerModels.java330
-rw-r--r--opendc-simulator/opendc-simulator-compute/src/main/java/org/opendc/simulator/compute/workload/SimFlopsWorkload.java141
-rw-r--r--opendc-simulator/opendc-simulator-compute/src/main/java/org/opendc/simulator/compute/workload/SimRuntimeWorkload.java131
-rw-r--r--opendc-simulator/opendc-simulator-compute/src/main/java/org/opendc/simulator/compute/workload/SimTrace.java422
-rw-r--r--opendc-simulator/opendc-simulator-compute/src/main/java/org/opendc/simulator/compute/workload/SimTraceFragment.java94
-rw-r--r--opendc-simulator/opendc-simulator-compute/src/main/java/org/opendc/simulator/compute/workload/SimWorkload.java (renamed from opendc-simulator/opendc-simulator-compute/src/main/kotlin/org/opendc/simulator/compute/workload/SimWorkload.kt)13
-rw-r--r--opendc-simulator/opendc-simulator-compute/src/main/java/org/opendc/simulator/compute/workload/SimWorkloadLifecycle.java (renamed from opendc-simulator/opendc-simulator-power/src/main/kotlin/org/opendc/simulator/power/SimPowerSource.kt)50
-rw-r--r--opendc-simulator/opendc-simulator-compute/src/main/kotlin/org/opendc/simulator/compute/SimAbstractMachine.kt239
-rw-r--r--opendc-simulator/opendc-simulator-compute/src/main/kotlin/org/opendc/simulator/compute/SimBareMetalMachine.kt130
-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/kernel/SimHypervisor.kt442
-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.kt36
-rw-r--r--opendc-simulator/opendc-simulator-compute/src/main/kotlin/org/opendc/simulator/compute/kernel/interference/VmInterferenceDomain.kt131
-rw-r--r--opendc-simulator/opendc-simulator-compute/src/main/kotlin/org/opendc/simulator/compute/kernel/interference/VmInterferenceMember.kt163
-rw-r--r--opendc-simulator/opendc-simulator-compute/src/main/kotlin/org/opendc/simulator/compute/kernel/interference/VmInterferenceModel.kt191
-rw-r--r--opendc-simulator/opendc-simulator-compute/src/main/kotlin/org/opendc/simulator/compute/model/MachineModel.kt54
-rw-r--r--opendc-simulator/opendc-simulator-compute/src/main/kotlin/org/opendc/simulator/compute/model/MemoryUnit.kt38
-rw-r--r--opendc-simulator/opendc-simulator-compute/src/main/kotlin/org/opendc/simulator/compute/model/NetworkAdapter.kt36
-rw-r--r--opendc-simulator/opendc-simulator-compute/src/main/kotlin/org/opendc/simulator/compute/model/ProcessingNode.kt38
-rw-r--r--opendc-simulator/opendc-simulator-compute/src/main/kotlin/org/opendc/simulator/compute/model/ProcessingUnit.kt36
-rw-r--r--opendc-simulator/opendc-simulator-compute/src/main/kotlin/org/opendc/simulator/compute/model/StorageDevice.kt40
-rw-r--r--opendc-simulator/opendc-simulator-compute/src/main/kotlin/org/opendc/simulator/compute/power/AsymptoticPowerModel.kt54
-rw-r--r--opendc-simulator/opendc-simulator-compute/src/main/kotlin/org/opendc/simulator/compute/power/CubicPowerModel.kt41
-rw-r--r--opendc-simulator/opendc-simulator-compute/src/main/kotlin/org/opendc/simulator/compute/power/InterpolationPowerModel.kt64
-rw-r--r--opendc-simulator/opendc-simulator-compute/src/main/kotlin/org/opendc/simulator/compute/power/LinearPowerModel.kt42
-rw-r--r--opendc-simulator/opendc-simulator-compute/src/main/kotlin/org/opendc/simulator/compute/power/MsePowerModel.kt49
-rw-r--r--opendc-simulator/opendc-simulator-compute/src/main/kotlin/org/opendc/simulator/compute/power/PStatePowerDriver.kt60
-rw-r--r--opendc-simulator/opendc-simulator-compute/src/main/kotlin/org/opendc/simulator/compute/power/PowerDriver.kt48
-rw-r--r--opendc-simulator/opendc-simulator-compute/src/main/kotlin/org/opendc/simulator/compute/power/SimplePowerDriver.kt48
-rw-r--r--opendc-simulator/opendc-simulator-compute/src/main/kotlin/org/opendc/simulator/compute/power/SqrtPowerModel.kt41
-rw-r--r--opendc-simulator/opendc-simulator-compute/src/main/kotlin/org/opendc/simulator/compute/power/SquarePowerModel.kt41
-rw-r--r--opendc-simulator/opendc-simulator-compute/src/main/kotlin/org/opendc/simulator/compute/power/ZeroIdlePowerDecorator.kt40
-rw-r--r--opendc-simulator/opendc-simulator-compute/src/main/kotlin/org/opendc/simulator/compute/workload/SimFlopsWorkload.kt54
-rw-r--r--opendc-simulator/opendc-simulator-compute/src/main/kotlin/org/opendc/simulator/compute/workload/SimRuntimeWorkload.kt54
-rw-r--r--opendc-simulator/opendc-simulator-compute/src/main/kotlin/org/opendc/simulator/compute/workload/SimTrace.kt218
-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.kt46
-rw-r--r--opendc-simulator/opendc-simulator-compute/src/main/kotlin/org/opendc/simulator/compute/workload/SimWorkloadLifecycle.kt82
-rw-r--r--opendc-simulator/opendc-simulator-compute/src/test/kotlin/org/opendc/simulator/compute/SimMachineTest.kt224
-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.kt146
-rw-r--r--opendc-simulator/opendc-simulator-compute/src/test/kotlin/org/opendc/simulator/compute/kernel/SimSpaceSharedHypervisorTest.kt86
-rw-r--r--opendc-simulator/opendc-simulator-compute/src/test/kotlin/org/opendc/simulator/compute/kernel/cpufreq/ConservativeScalingGovernorTest.kt14
-rw-r--r--opendc-simulator/opendc-simulator-compute/src/test/kotlin/org/opendc/simulator/compute/kernel/cpufreq/OnDemandScalingGovernorTest.kt13
-rw-r--r--opendc-simulator/opendc-simulator-compute/src/test/kotlin/org/opendc/simulator/compute/kernel/cpufreq/PerformanceScalingGovernorTest.kt2
-rw-r--r--opendc-simulator/opendc-simulator-compute/src/test/kotlin/org/opendc/simulator/compute/kernel/cpufreq/PowerSaveScalingGovernorTest.kt4
-rw-r--r--opendc-simulator/opendc-simulator-compute/src/test/kotlin/org/opendc/simulator/compute/power/PStatePowerDriverTest.kt123
-rw-r--r--opendc-simulator/opendc-simulator-compute/src/test/kotlin/org/opendc/simulator/compute/power/PowerModelTest.kt27
-rw-r--r--opendc-simulator/opendc-simulator-compute/src/test/kotlin/org/opendc/simulator/compute/workload/SimFlopsWorkloadTest.kt2
-rw-r--r--opendc-simulator/opendc-simulator-compute/src/test/kotlin/org/opendc/simulator/compute/workload/SimTraceWorkloadTest.kt72
-rw-r--r--opendc-simulator/opendc-simulator-compute/src/test/resources/spec_machines.yml29
-rw-r--r--opendc-simulator/opendc-simulator-flow/src/jmh/kotlin/org/opendc/simulator/flow/FlowBenchmarks.kt137
-rw-r--r--opendc-simulator/opendc-simulator-flow/src/jmh/kotlin/org/opendc/simulator/flow2/FlowBenchmarks.kt124
-rw-r--r--opendc-simulator/opendc-simulator-flow/src/main/java/org/opendc/simulator/flow2/FlowEngine.java260
-rw-r--r--opendc-simulator/opendc-simulator-flow/src/main/java/org/opendc/simulator/flow2/FlowGraph.java63
-rw-r--r--opendc-simulator/opendc-simulator-flow/src/main/java/org/opendc/simulator/flow2/FlowGraphInternal.java93
-rw-r--r--opendc-simulator/opendc-simulator-flow/src/main/java/org/opendc/simulator/flow2/FlowStage.java303
-rw-r--r--opendc-simulator/opendc-simulator-flow/src/main/java/org/opendc/simulator/flow2/FlowStageLogic.java38
-rw-r--r--opendc-simulator/opendc-simulator-flow/src/main/java/org/opendc/simulator/flow2/FlowStageQueue.java109
-rw-r--r--opendc-simulator/opendc-simulator-flow/src/main/java/org/opendc/simulator/flow2/FlowTimerQueue.java208
-rw-r--r--opendc-simulator/opendc-simulator-flow/src/main/java/org/opendc/simulator/flow2/InHandler.java54
-rw-r--r--opendc-simulator/opendc-simulator-flow/src/main/java/org/opendc/simulator/flow2/InHandlers.java53
-rw-r--r--opendc-simulator/opendc-simulator-flow/src/main/java/org/opendc/simulator/flow2/InPort.java214
-rw-r--r--opendc-simulator/opendc-simulator-flow/src/main/java/org/opendc/simulator/flow2/Inlet.java (renamed from opendc-simulator/opendc-simulator-compute/src/main/kotlin/org/opendc/simulator/compute/power/ConstantPowerModel.kt)18
-rw-r--r--opendc-simulator/opendc-simulator-flow/src/main/java/org/opendc/simulator/flow2/InvocationStack.java95
-rw-r--r--opendc-simulator/opendc-simulator-flow/src/main/java/org/opendc/simulator/flow2/OutHandler.java47
-rw-r--r--opendc-simulator/opendc-simulator-flow/src/main/java/org/opendc/simulator/flow2/OutHandlers.java53
-rw-r--r--opendc-simulator/opendc-simulator-flow/src/main/java/org/opendc/simulator/flow2/OutPort.java224
-rw-r--r--opendc-simulator/opendc-simulator-flow/src/main/java/org/opendc/simulator/flow2/Outlet.java38
-rw-r--r--opendc-simulator/opendc-simulator-flow/src/main/java/org/opendc/simulator/flow2/mux/FlowMultiplexer.java95
-rw-r--r--opendc-simulator/opendc-simulator-flow/src/main/java/org/opendc/simulator/flow2/mux/FlowMultiplexerFactory.java (renamed from opendc-simulator/opendc-simulator-compute/src/main/kotlin/org/opendc/simulator/compute/SimMachineContext.kt)45
-rw-r--r--opendc-simulator/opendc-simulator-flow/src/main/java/org/opendc/simulator/flow2/mux/ForwardingFlowMultiplexer.java280
-rw-r--r--opendc-simulator/opendc-simulator-flow/src/main/java/org/opendc/simulator/flow2/mux/MaxMinFlowMultiplexer.java297
-rw-r--r--opendc-simulator/opendc-simulator-flow/src/main/java/org/opendc/simulator/flow2/sink/FlowSink.java (renamed from opendc-simulator/opendc-simulator-flow/src/main/kotlin/org/opendc/simulator/flow/internal/Constants.kt)16
-rw-r--r--opendc-simulator/opendc-simulator-flow/src/main/java/org/opendc/simulator/flow2/sink/SimpleFlowSink.java123
-rw-r--r--opendc-simulator/opendc-simulator-flow/src/main/java/org/opendc/simulator/flow2/source/EmptyFlowSource.java (renamed from opendc-simulator/opendc-simulator-flow/src/main/kotlin/org/opendc/simulator/flow/FlowConsumerContext.kt)57
-rw-r--r--opendc-simulator/opendc-simulator-flow/src/main/java/org/opendc/simulator/flow2/source/FlowSource.java36
-rw-r--r--opendc-simulator/opendc-simulator-flow/src/main/java/org/opendc/simulator/flow2/source/RuntimeFlowSource.java128
-rw-r--r--opendc-simulator/opendc-simulator-flow/src/main/java/org/opendc/simulator/flow2/source/SimpleFlowSource.java131
-rw-r--r--opendc-simulator/opendc-simulator-flow/src/main/java/org/opendc/simulator/flow2/source/TraceFlowSource.java151
-rw-r--r--opendc-simulator/opendc-simulator-flow/src/main/java/org/opendc/simulator/flow2/util/FlowTransform.java (renamed from opendc-simulator/opendc-simulator-compute/src/main/kotlin/org/opendc/simulator/compute/SimProcessingUnit.kt)20
-rw-r--r--opendc-simulator/opendc-simulator-flow/src/main/java/org/opendc/simulator/flow2/util/FlowTransformer.java124
-rw-r--r--opendc-simulator/opendc-simulator-flow/src/main/java/org/opendc/simulator/flow2/util/FlowTransforms.java (renamed from opendc-simulator/opendc-simulator-flow/src/main/kotlin/org/opendc/simulator/flow/source/FlowSourceBarrier.kt)43
-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/FlowConsumerLogic.kt57
-rw-r--r--opendc-simulator/opendc-simulator-flow/src/main/kotlin/org/opendc/simulator/flow/FlowCounters.kt48
-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.kt264
-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.kt160
-rw-r--r--opendc-simulator/opendc-simulator-flow/src/main/kotlin/org/opendc/simulator/flow/FlowSource.kt67
-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.kt436
-rw-r--r--opendc-simulator/opendc-simulator-flow/src/main/kotlin/org/opendc/simulator/flow/internal/FlowDeque.kt119
-rw-r--r--opendc-simulator/opendc-simulator-flow/src/main/kotlin/org/opendc/simulator/flow/internal/FlowEngineImpl.kt218
-rw-r--r--opendc-simulator/opendc-simulator-flow/src/main/kotlin/org/opendc/simulator/flow/internal/FlowTimerQueue.kt200
-rw-r--r--opendc-simulator/opendc-simulator-flow/src/main/kotlin/org/opendc/simulator/flow/internal/MutableFlowCounters.kt53
-rw-r--r--opendc-simulator/opendc-simulator-flow/src/main/kotlin/org/opendc/simulator/flow/mux/FlowMultiplexer.kt124
-rw-r--r--opendc-simulator/opendc-simulator-flow/src/main/kotlin/org/opendc/simulator/flow/mux/FlowMultiplexerFactory.kt60
-rw-r--r--opendc-simulator/opendc-simulator-flow/src/main/kotlin/org/opendc/simulator/flow/mux/ForwardingFlowMultiplexer.kt177
-rw-r--r--opendc-simulator/opendc-simulator-flow/src/main/kotlin/org/opendc/simulator/flow/mux/MaxMinFlowMultiplexer.kt811
-rw-r--r--opendc-simulator/opendc-simulator-flow/src/main/kotlin/org/opendc/simulator/flow/source/FixedFlowSource.kt66
-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.kt107
-rw-r--r--opendc-simulator/opendc-simulator-flow/src/test/kotlin/org/opendc/simulator/flow/FlowForwarderTest.kt331
-rw-r--r--opendc-simulator/opendc-simulator-flow/src/test/kotlin/org/opendc/simulator/flow/FlowSinkTest.kt245
-rw-r--r--opendc-simulator/opendc-simulator-flow/src/test/kotlin/org/opendc/simulator/flow/mux/ForwardingFlowMultiplexerTest.kt158
-rw-r--r--opendc-simulator/opendc-simulator-flow/src/test/kotlin/org/opendc/simulator/flow/mux/MaxMinFlowMultiplexerTest.kt150
-rw-r--r--opendc-simulator/opendc-simulator-flow/src/test/kotlin/org/opendc/simulator/flow2/FlowEngineTest.kt197
-rw-r--r--opendc-simulator/opendc-simulator-flow/src/test/kotlin/org/opendc/simulator/flow2/FlowTimerQueueTest.kt385
-rw-r--r--opendc-simulator/opendc-simulator-flow/src/test/kotlin/org/opendc/simulator/flow2/InvocationStackTest.kt71
-rw-r--r--opendc-simulator/opendc-simulator-flow/src/test/kotlin/org/opendc/simulator/flow2/mux/ForwardingFlowMultiplexerTest.kt66
-rw-r--r--opendc-simulator/opendc-simulator-flow/src/test/kotlin/org/opendc/simulator/flow2/mux/MaxMinFlowMultiplexerTest.kt (renamed from opendc-simulator/opendc-simulator-flow/src/test/kotlin/org/opendc/simulator/flow/source/FixedFlowSourceTest.kt)39
-rw-r--r--opendc-simulator/opendc-simulator-flow/src/test/kotlin/org/opendc/simulator/flow2/sink/FlowSinkTest.kt124
-rw-r--r--opendc-simulator/opendc-simulator-network/src/main/java/org/opendc/simulator/network/SimNetworkLink.java77
-rw-r--r--opendc-simulator/opendc-simulator-network/src/main/java/org/opendc/simulator/network/SimNetworkPort.java110
-rw-r--r--opendc-simulator/opendc-simulator-network/src/main/java/org/opendc/simulator/network/SimNetworkSink.java70
-rw-r--r--opendc-simulator/opendc-simulator-network/src/main/java/org/opendc/simulator/network/SimNetworkSwitch.java (renamed from opendc-simulator/opendc-simulator-network/src/main/kotlin/org/opendc/simulator/network/SimNetworkSwitch.kt)10
-rw-r--r--opendc-simulator/opendc-simulator-network/src/main/java/org/opendc/simulator/network/SimNetworkSwitchVirtual.java107
-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/SimNetworkSwitchVirtual.kt78
-rw-r--r--opendc-simulator/opendc-simulator-network/src/test/kotlin/org/opendc/simulator/network/SimNetworkSinkTest.kt91
-rw-r--r--opendc-simulator/opendc-simulator-network/src/test/kotlin/org/opendc/simulator/network/SimNetworkSwitchVirtualTest.kt47
-rw-r--r--opendc-simulator/opendc-simulator-network/src/test/kotlin/org/opendc/simulator/network/TestSource.kt (renamed from opendc-simulator/opendc-simulator-network/src/main/kotlin/org/opendc/simulator/network/SimNetworkSink.kt)40
-rw-r--r--opendc-simulator/opendc-simulator-power/src/main/java/org/opendc/simulator/power/SimPdu.java141
-rw-r--r--opendc-simulator/opendc-simulator-power/src/main/java/org/opendc/simulator/power/SimPowerInlet.java (renamed from opendc-simulator/opendc-simulator-power/src/main/kotlin/org/opendc/simulator/power/SimPowerInlet.kt)29
-rw-r--r--opendc-simulator/opendc-simulator-power/src/main/java/org/opendc/simulator/power/SimPowerOutlet.java91
-rw-r--r--opendc-simulator/opendc-simulator-power/src/main/java/org/opendc/simulator/power/SimPowerSource.java71
-rw-r--r--opendc-simulator/opendc-simulator-power/src/main/java/org/opendc/simulator/power/SimUps.java137
-rw-r--r--opendc-simulator/opendc-simulator-power/src/main/kotlin/org/opendc/simulator/power/SimPdu.kt95
-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/SimUps.kt101
-rw-r--r--opendc-simulator/opendc-simulator-power/src/test/kotlin/org/opendc/simulator/power/SimPduTest.kt92
-rw-r--r--opendc-simulator/opendc-simulator-power/src/test/kotlin/org/opendc/simulator/power/SimPowerSourceTest.kt91
-rw-r--r--opendc-simulator/opendc-simulator-power/src/test/kotlin/org/opendc/simulator/power/SimUpsTest.kt71
-rw-r--r--opendc-simulator/opendc-simulator-power/src/test/kotlin/org/opendc/simulator/power/TestInlet.kt (renamed from opendc-simulator/opendc-simulator-compute/src/main/kotlin/org/opendc/simulator/compute/kernel/cpufreq/PowerSaveScalingGovernor.kt)30
172 files changed, 10314 insertions, 8912 deletions
diff --git a/opendc-simulator/opendc-simulator-compute/src/jmh/kotlin/org/opendc/simulator/compute/SimMachineBenchmarks.kt b/opendc-simulator/opendc-simulator-compute/src/jmh/kotlin/org/opendc/simulator/compute/SimMachineBenchmarks.kt
index 220b97cc..ec032070 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
@@ -29,12 +29,9 @@ 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.flow.FlowEngine
-import org.opendc.simulator.flow.mux.FlowMultiplexerFactory
+import org.opendc.simulator.flow2.FlowEngine
+import org.opendc.simulator.flow2.mux.FlowMultiplexerFactory
import org.opendc.simulator.kotlin.runSimulation
import org.openjdk.jmh.annotations.Benchmark
import org.openjdk.jmh.annotations.Fork
@@ -60,14 +57,14 @@ class SimMachineBenchmarks {
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) }
+ /*cpus*/ List(cpuNode.coreCount) { ProcessingUnit(cpuNode, it, 1000.0) },
+ /*memory*/ List(4) { MemoryUnit("Crucial", "MTA18ASF4G72AZ-3G2B1", 3200.0, 32_000) }
)
val random = ThreadLocalRandom.current()
val builder = SimTrace.builder()
- repeat(10000) {
- val timestamp = it.toLong()
+ repeat(1000000) {
+ val timestamp = it.toLong() * 1000
val deadline = timestamp + 1000
builder.add(deadline, random.nextDouble(0.0, 4500.0), 1)
}
@@ -77,29 +74,27 @@ class SimMachineBenchmarks {
@Benchmark
fun benchmarkBareMetal() {
return runSimulation {
- val engine = FlowEngine(coroutineContext, clock)
- val machine = SimBareMetalMachine(
- engine,
- machineModel,
- SimplePowerDriver(ConstantPowerModel(0.0))
- )
- return@runSimulation machine.runWorkload(SimTraceWorkload(trace))
+ val engine = FlowEngine.create(coroutineContext, clock)
+ val graph = engine.newGraph()
+ val machine = SimBareMetalMachine.create(graph, machineModel)
+ return@runSimulation machine.runWorkload(trace.createWorkload(0))
}
}
@Benchmark
fun benchmarkSpaceSharedHypervisor() {
return runSimulation {
- val engine = FlowEngine(coroutineContext, clock)
- val machine = SimBareMetalMachine(engine, machineModel, SimplePowerDriver(ConstantPowerModel(0.0)))
- val hypervisor = SimHypervisor(engine, FlowMultiplexerFactory.forwardingMultiplexer(), SplittableRandom(1), null)
+ val engine = FlowEngine.create(coroutineContext, clock)
+ val graph = engine.newGraph()
+ val machine = SimBareMetalMachine.create(graph, machineModel)
+ val hypervisor = SimHypervisor.create(FlowMultiplexerFactory.forwardingMultiplexer(), SplittableRandom(1))
launch { machine.runWorkload(hypervisor) }
val vm = hypervisor.newMachine(machineModel)
try {
- return@runSimulation vm.runWorkload(SimTraceWorkload(trace))
+ return@runSimulation vm.runWorkload(trace.createWorkload(0))
} finally {
vm.cancel()
machine.cancel()
@@ -110,16 +105,17 @@ class SimMachineBenchmarks {
@Benchmark
fun benchmarkFairShareHypervisorSingle() {
return runSimulation {
- val engine = FlowEngine(coroutineContext, clock)
- val machine = SimBareMetalMachine(engine, machineModel, SimplePowerDriver(ConstantPowerModel(0.0)))
- val hypervisor = SimHypervisor(engine, FlowMultiplexerFactory.maxMinMultiplexer(), SplittableRandom(1), null)
+ val engine = FlowEngine.create(coroutineContext, clock)
+ val graph = engine.newGraph()
+ val machine = SimBareMetalMachine.create(graph, machineModel)
+ val hypervisor = SimHypervisor.create(FlowMultiplexerFactory.maxMinMultiplexer(), SplittableRandom(1))
launch { machine.runWorkload(hypervisor) }
val vm = hypervisor.newMachine(machineModel)
try {
- return@runSimulation vm.runWorkload(SimTraceWorkload(trace))
+ return@runSimulation vm.runWorkload(trace.createWorkload(0))
} finally {
vm.cancel()
machine.cancel()
@@ -130,9 +126,10 @@ class SimMachineBenchmarks {
@Benchmark
fun benchmarkFairShareHypervisorDouble() {
return runSimulation {
- val engine = FlowEngine(coroutineContext, clock)
- val machine = SimBareMetalMachine(engine, machineModel, SimplePowerDriver(ConstantPowerModel(0.0)))
- val hypervisor = SimHypervisor(engine, FlowMultiplexerFactory.maxMinMultiplexer(), SplittableRandom(1), null)
+ val engine = FlowEngine.create(coroutineContext, clock)
+ val graph = engine.newGraph()
+ val machine = SimBareMetalMachine.create(graph, machineModel)
+ val hypervisor = SimHypervisor.create(FlowMultiplexerFactory.maxMinMultiplexer(), SplittableRandom(1))
launch { machine.runWorkload(hypervisor) }
@@ -142,7 +139,7 @@ class SimMachineBenchmarks {
launch {
try {
- vm.runWorkload(SimTraceWorkload(trace))
+ vm.runWorkload(trace.createWorkload(0))
} finally {
machine.cancel()
}
diff --git a/opendc-simulator/opendc-simulator-compute/src/main/java/org/opendc/simulator/compute/SimAbstractMachine.java b/opendc-simulator/opendc-simulator-compute/src/main/java/org/opendc/simulator/compute/SimAbstractMachine.java
new file mode 100644
index 00000000..cf5aed03
--- /dev/null
+++ b/opendc-simulator/opendc-simulator-compute/src/main/java/org/opendc/simulator/compute/SimAbstractMachine.java
@@ -0,0 +1,327 @@
+/*
+ * Copyright (c) 2022 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 java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+import org.opendc.simulator.compute.device.SimNetworkAdapter;
+import org.opendc.simulator.compute.model.MachineModel;
+import org.opendc.simulator.compute.model.MemoryUnit;
+import org.opendc.simulator.compute.workload.SimWorkload;
+import org.opendc.simulator.flow2.FlowGraph;
+import org.opendc.simulator.flow2.Inlet;
+import org.opendc.simulator.flow2.Outlet;
+import org.opendc.simulator.flow2.sink.SimpleFlowSink;
+import org.opendc.simulator.flow2.util.FlowTransformer;
+import org.opendc.simulator.flow2.util.FlowTransforms;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+/**
+ * Abstract implementation of the {@link SimMachine} interface.
+ */
+public abstract class SimAbstractMachine implements SimMachine {
+ private static final Logger LOGGER = LoggerFactory.getLogger(SimAbstractMachine.class);
+ private final MachineModel model;
+
+ private Context activeContext;
+
+ /**
+ * Construct a {@link SimAbstractMachine} instance.
+ *
+ * @param model The model of the machine.
+ */
+ public SimAbstractMachine(MachineModel model) {
+ this.model = model;
+ }
+
+ @Override
+ public final MachineModel getModel() {
+ return model;
+ }
+
+ @Override
+ public final SimMachineContext startWorkload(SimWorkload workload, Map<String, Object> meta) {
+ if (activeContext != null) {
+ throw new IllegalStateException("A machine cannot run multiple workloads concurrently");
+ }
+
+ final Context ctx = createContext(workload, new HashMap<>(meta));
+ ctx.start();
+ return ctx;
+ }
+
+ @Override
+ public final void cancel() {
+ final Context context = activeContext;
+ if (context != null) {
+ context.shutdown();
+ }
+ }
+
+ /**
+ * Construct a new {@link Context} instance representing the active execution.
+ *
+ * @param workload The workload to start on the machine.
+ * @param meta The metadata to pass to the workload.
+ */
+ protected abstract Context createContext(SimWorkload workload, Map<String, Object> meta);
+
+ /**
+ * Return the active {@link Context} instance (if any).
+ */
+ protected Context getActiveContext() {
+ return activeContext;
+ }
+
+ /**
+ * The execution context in which the workload runs.
+ */
+ public abstract static class Context implements SimMachineContext {
+ private final SimAbstractMachine machine;
+ private final SimWorkload workload;
+ private final Map<String, Object> meta;
+ private boolean isClosed;
+
+ /**
+ * Construct a new {@link Context} instance.
+ *
+ * @param machine The {@link SimAbstractMachine} to which the context belongs.
+ * @param workload The {@link SimWorkload} to which the context belongs.
+ * @param meta The metadata passed to the context.
+ */
+ public Context(SimAbstractMachine machine, SimWorkload workload, Map<String, Object> meta) {
+ this.machine = machine;
+ this.workload = workload;
+ this.meta = meta;
+ }
+
+ @Override
+ public final Map<String, Object> getMeta() {
+ return meta;
+ }
+
+ @Override
+ public final void shutdown() {
+ if (isClosed) {
+ return;
+ }
+
+ isClosed = true;
+ final SimAbstractMachine machine = this.machine;
+ assert machine.activeContext == this : "Invariant violation: multiple contexts active for a single machine";
+ machine.activeContext = null;
+
+ // Cancel all the resources associated with the machine
+ doCancel();
+
+ try {
+ workload.onStop(this);
+ } catch (Exception cause) {
+ LOGGER.warn("Workload failed during onStop callback", cause);
+ }
+ }
+
+ /**
+ * Start this context.
+ */
+ final void start() {
+ try {
+ machine.activeContext = this;
+ workload.onStart(this);
+ } catch (Exception cause) {
+ LOGGER.warn("Workload failed during onStart callback", cause);
+ shutdown();
+ }
+ }
+
+ /**
+ * Run the stop procedures for the resources associated with the machine.
+ */
+ protected void doCancel() {
+ final FlowGraph graph = getMemory().getInput().getGraph();
+
+ for (SimProcessingUnit cpu : getCpus()) {
+ final Inlet inlet = cpu.getInput();
+ graph.disconnect(inlet);
+ }
+
+ graph.disconnect(getMemory().getInput());
+
+ for (SimNetworkInterface ifx : getNetworkInterfaces()) {
+ ((NetworkAdapter) ifx).disconnect();
+ }
+
+ for (SimStorageInterface storage : getStorageInterfaces()) {
+ StorageDevice impl = (StorageDevice) storage;
+ graph.disconnect(impl.getRead());
+ graph.disconnect(impl.getWrite());
+ }
+ }
+
+ @Override
+ public String toString() {
+ return "SimAbstractMachine.Context";
+ }
+ }
+
+ /**
+ * The [SimMemory] implementation for a machine.
+ */
+ public static final class Memory implements SimMemory {
+ private final SimpleFlowSink sink;
+ private final List<MemoryUnit> models;
+
+ public Memory(FlowGraph graph, List<MemoryUnit> models) {
+ long memorySize = 0;
+ for (MemoryUnit mem : models) {
+ memorySize += mem.getSize();
+ }
+
+ this.sink = new SimpleFlowSink(graph, (float) memorySize);
+ this.models = models;
+ }
+
+ @Override
+ public double getCapacity() {
+ return sink.getCapacity();
+ }
+
+ @Override
+ public List<MemoryUnit> getModels() {
+ return models;
+ }
+
+ @Override
+ public Inlet getInput() {
+ return sink.getInput();
+ }
+
+ @Override
+ public String toString() {
+ return "SimAbstractMachine.Memory";
+ }
+ }
+
+ /**
+ * A {@link SimNetworkAdapter} implementation for a machine.
+ */
+ public static class NetworkAdapter extends SimNetworkAdapter implements SimNetworkInterface {
+ private final org.opendc.simulator.compute.model.NetworkAdapter model;
+ private final FlowTransformer tx;
+ private final FlowTransformer rx;
+ private final String name;
+
+ /**
+ * Construct a {@link NetworkAdapter}.
+ */
+ public NetworkAdapter(FlowGraph graph, org.opendc.simulator.compute.model.NetworkAdapter model, int index) {
+ this.model = model;
+ this.tx = new FlowTransformer(graph, FlowTransforms.noop());
+ this.rx = new FlowTransformer(graph, FlowTransforms.noop());
+ this.name = "eth" + index;
+ }
+
+ @Override
+ public String getName() {
+ return name;
+ }
+
+ @Override
+ public Inlet getTx() {
+ return tx.getInput();
+ }
+
+ @Override
+ public Outlet getRx() {
+ return rx.getOutput();
+ }
+
+ @Override
+ public double getBandwidth() {
+ return model.getBandwidth();
+ }
+
+ @Override
+ protected Outlet getOutlet() {
+ return tx.getOutput();
+ }
+
+ @Override
+ protected Inlet getInlet() {
+ return rx.getInput();
+ }
+
+ @Override
+ public String toString() {
+ return "SimAbstractMachine.NetworkAdapterImpl[name=" + name + ", bandwidth=" + model.getBandwidth()
+ + "Mbps]";
+ }
+ }
+
+ /**
+ * A {@link SimStorageInterface} implementation for a machine.
+ */
+ public static class StorageDevice implements SimStorageInterface {
+ private final org.opendc.simulator.compute.model.StorageDevice model;
+ private final SimpleFlowSink read;
+ private final SimpleFlowSink write;
+ private final String name;
+
+ /**
+ * Construct a {@link StorageDevice}.
+ */
+ public StorageDevice(FlowGraph graph, org.opendc.simulator.compute.model.StorageDevice model, int index) {
+ this.model = model;
+ this.read = new SimpleFlowSink(graph, (float) model.getReadBandwidth());
+ this.write = new SimpleFlowSink(graph, (float) model.getWriteBandwidth());
+ this.name = "disk" + index;
+ }
+
+ @Override
+ public String getName() {
+ return name;
+ }
+
+ @Override
+ public Inlet getRead() {
+ return read.getInput();
+ }
+
+ @Override
+ public Inlet getWrite() {
+ return write.getInput();
+ }
+
+ @Override
+ public double getCapacity() {
+ return model.getCapacity();
+ }
+
+ @Override
+ public String toString() {
+ return "SimAbstractMachine.StorageDeviceImpl[name=" + name + ", capacity=" + model.getCapacity() + "MB]";
+ }
+ }
+}
diff --git a/opendc-simulator/opendc-simulator-compute/src/main/java/org/opendc/simulator/compute/SimBareMetalMachine.java b/opendc-simulator/opendc-simulator-compute/src/main/java/org/opendc/simulator/compute/SimBareMetalMachine.java
new file mode 100644
index 00000000..aa7502d6
--- /dev/null
+++ b/opendc-simulator/opendc-simulator-compute/src/main/java/org/opendc/simulator/compute/SimBareMetalMachine.java
@@ -0,0 +1,298 @@
+/*
+ * Copyright (c) 2022 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 java.util.ArrayList;
+import java.util.Collections;
+import java.util.List;
+import java.util.Map;
+import org.opendc.simulator.compute.device.SimPeripheral;
+import org.opendc.simulator.compute.model.MachineModel;
+import org.opendc.simulator.compute.model.ProcessingUnit;
+import org.opendc.simulator.compute.workload.SimWorkload;
+import org.opendc.simulator.flow2.FlowGraph;
+import org.opendc.simulator.flow2.InPort;
+import org.opendc.simulator.flow2.Inlet;
+
+/**
+ * A simulated bare-metal machine that is able to run a single workload.
+ *
+ * <p>
+ * A {@link 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 {@link #startWorkload(SimWorkload, Map)}.
+ */
+public final class SimBareMetalMachine extends SimAbstractMachine {
+ /**
+ * The {@link FlowGraph} in which the simulation takes places.
+ */
+ private final FlowGraph graph;
+
+ /**
+ * The {@link SimPsu} of this bare metal machine.
+ */
+ private final SimPsu psu;
+
+ /**
+ * The resources of this machine.
+ */
+ private final List<Cpu> cpus;
+
+ private final Memory memory;
+ private final List<NetworkAdapter> net;
+ private final List<StorageDevice> disk;
+
+ /**
+ * Construct a {@link SimBareMetalMachine} instance.
+ *
+ * @param graph The {@link FlowGraph} to which the machine belongs.
+ * @param model The machine model to simulate.
+ * @param psuFactory The {@link SimPsuFactory} to construct the power supply of the machine.
+ */
+ private SimBareMetalMachine(FlowGraph graph, MachineModel model, SimPsuFactory psuFactory) {
+ super(model);
+
+ this.graph = graph;
+ this.psu = psuFactory.newPsu(this, graph);
+
+ int cpuIndex = 0;
+ final ArrayList<Cpu> cpus = new ArrayList<>();
+ this.cpus = cpus;
+ for (ProcessingUnit cpu : model.getCpus()) {
+ cpus.add(new Cpu(psu, cpu, cpuIndex++));
+ }
+
+ this.memory = new Memory(graph, model.getMemory());
+
+ int netIndex = 0;
+ final ArrayList<NetworkAdapter> net = new ArrayList<>();
+ this.net = net;
+ for (org.opendc.simulator.compute.model.NetworkAdapter adapter : model.getNetwork()) {
+ net.add(new NetworkAdapter(graph, adapter, netIndex++));
+ }
+
+ int diskIndex = 0;
+ final ArrayList<StorageDevice> disk = new ArrayList<>();
+ this.disk = disk;
+ for (org.opendc.simulator.compute.model.StorageDevice device : model.getStorage()) {
+ disk.add(new StorageDevice(graph, device, diskIndex++));
+ }
+ }
+
+ /**
+ * Create a {@link SimBareMetalMachine} instance.
+ *
+ * @param graph The {@link FlowGraph} to which the machine belongs.
+ * @param model The machine model to simulate.
+ * @param psuFactory The {@link SimPsuFactory} to construct the power supply of the machine.
+ */
+ public static SimBareMetalMachine create(FlowGraph graph, MachineModel model, SimPsuFactory psuFactory) {
+ return new SimBareMetalMachine(graph, model, psuFactory);
+ }
+
+ /**
+ * Create a {@link SimBareMetalMachine} instance with a no-op PSU.
+ *
+ * @param graph The {@link FlowGraph} to which the machine belongs.
+ * @param model The machine model to simulate.
+ */
+ public static SimBareMetalMachine create(FlowGraph graph, MachineModel model) {
+ return new SimBareMetalMachine(graph, model, SimPsuFactories.noop());
+ }
+
+ /**
+ * Return the {@link SimPsu} belonging to this bare metal machine.
+ */
+ public SimPsu getPsu() {
+ return psu;
+ }
+
+ /**
+ * Return the list of peripherals attached to this bare metal machine.
+ */
+ @Override
+ public List<? extends SimPeripheral> getPeripherals() {
+ return Collections.unmodifiableList(net);
+ }
+
+ /**
+ * Return the CPU capacity of the machine in MHz.
+ */
+ public double getCpuCapacity() {
+ final Context context = (Context) getActiveContext();
+
+ if (context == null) {
+ return 0.0;
+ }
+
+ float capacity = 0.f;
+
+ for (SimProcessingUnit cpu : context.cpus) {
+ capacity += cpu.getFrequency();
+ }
+
+ return capacity;
+ }
+
+ /**
+ * The CPU demand of the machine in MHz.
+ */
+ public double getCpuDemand() {
+ final Context context = (Context) getActiveContext();
+
+ if (context == null) {
+ return 0.0;
+ }
+
+ float demand = 0.f;
+
+ for (SimProcessingUnit cpu : context.cpus) {
+ demand += cpu.getDemand();
+ }
+
+ return demand;
+ }
+
+ /**
+ * The CPU usage of the machine in MHz.
+ */
+ public double getCpuUsage() {
+ final Context context = (Context) getActiveContext();
+
+ if (context == null) {
+ return 0.0;
+ }
+
+ float rate = 0.f;
+
+ for (SimProcessingUnit cpu : context.cpus) {
+ rate += cpu.getSpeed();
+ }
+
+ return rate;
+ }
+
+ @Override
+ protected SimAbstractMachine.Context createContext(SimWorkload workload, Map<String, Object> meta) {
+ return new Context(this, workload, meta);
+ }
+
+ /**
+ * The execution context for a {@link SimBareMetalMachine}.
+ */
+ private static final class Context extends SimAbstractMachine.Context {
+ private final FlowGraph graph;
+ private final List<Cpu> cpus;
+ private final Memory memory;
+ private final List<NetworkAdapter> net;
+ private final List<StorageDevice> disk;
+
+ private Context(SimBareMetalMachine machine, SimWorkload workload, Map<String, Object> meta) {
+ super(machine, workload, meta);
+
+ this.graph = machine.graph;
+ this.cpus = machine.cpus;
+ this.memory = machine.memory;
+ this.net = machine.net;
+ this.disk = machine.disk;
+ }
+
+ @Override
+ public FlowGraph getGraph() {
+ return graph;
+ }
+
+ @Override
+ public List<? extends SimProcessingUnit> getCpus() {
+ return cpus;
+ }
+
+ @Override
+ public SimMemory getMemory() {
+ return memory;
+ }
+
+ @Override
+ public List<? extends SimNetworkInterface> getNetworkInterfaces() {
+ return net;
+ }
+
+ @Override
+ public List<? extends SimStorageInterface> getStorageInterfaces() {
+ return disk;
+ }
+ }
+
+ /**
+ * A {@link SimProcessingUnit} of a bare-metal machine.
+ */
+ private static final class Cpu implements SimProcessingUnit {
+ private final SimPsu psu;
+ private final ProcessingUnit model;
+ private final InPort port;
+
+ private Cpu(SimPsu psu, ProcessingUnit model, int id) {
+ this.psu = psu;
+ this.model = model;
+ this.port = psu.getCpuPower(id, model);
+
+ this.port.pull((float) model.getFrequency());
+ }
+
+ @Override
+ public double getFrequency() {
+ return port.getCapacity();
+ }
+
+ @Override
+ public void setFrequency(double frequency) {
+ // Clamp the capacity of the CPU between [0.0, maxFreq]
+ frequency = Math.max(0, Math.min(model.getFrequency(), frequency));
+ psu.setCpuFrequency(port, frequency);
+ }
+
+ @Override
+ public double getDemand() {
+ return port.getDemand();
+ }
+
+ @Override
+ public double getSpeed() {
+ return port.getRate();
+ }
+
+ @Override
+ public ProcessingUnit getModel() {
+ return model;
+ }
+
+ @Override
+ public Inlet getInput() {
+ return port;
+ }
+
+ @Override
+ public String toString() {
+ return "SimBareMetalMachine.Cpu[model=" + model + "]";
+ }
+ }
+}
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/java/org/opendc/simulator/compute/SimMachine.java
index 94581e89..59599875 100644
--- a/opendc-simulator/opendc-simulator-compute/src/main/kotlin/org/opendc/simulator/compute/SimMachine.kt
+++ b/opendc-simulator/opendc-simulator-compute/src/main/java/org/opendc/simulator/compute/SimMachine.java
@@ -1,5 +1,5 @@
/*
- * Copyright (c) 2020 AtLarge Research
+ * Copyright (c) 2022 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,40 +20,40 @@
* SOFTWARE.
*/
-package org.opendc.simulator.compute
+package org.opendc.simulator.compute;
-import org.opendc.simulator.compute.device.SimPeripheral
-import org.opendc.simulator.compute.model.MachineModel
-import org.opendc.simulator.compute.workload.SimWorkload
+import java.util.List;
+import java.util.Map;
+import org.opendc.simulator.compute.device.SimPeripheral;
+import org.opendc.simulator.compute.model.MachineModel;
+import org.opendc.simulator.compute.workload.SimWorkload;
/**
- * A generic machine that is able to run a [SimWorkload].
+ * A generic machine that is able to execute {@link SimWorkload} objects.
*/
public interface SimMachine {
/**
- * The model of the machine containing its specifications.
+ * Return the model of the machine containing its specifications.
*/
- public val model: MachineModel
+ MachineModel getModel();
/**
- * The peripherals attached to the machine.
+ * Return the peripherals attached to the machine.
*/
- public val peripherals: List<SimPeripheral>
+ List<? extends SimPeripheral> getPeripherals();
/**
- * Start the specified [SimWorkload] on this machine.
+ * Start the specified {@link SimWorkload} on this machine.
*
* @param workload The workload to start on the machine.
* @param meta The metadata to pass to the workload.
- * @return A [SimMachineContext] that represents the execution context for the workload.
+ * @return A {@link SimMachineContext} that represents the execution context for the workload.
* @throws IllegalStateException if a workload is already active on the machine or if the machine is closed.
*/
- public fun startWorkload(workload: SimWorkload, meta: Map<String, Any> = emptyMap()): SimMachineContext
+ SimMachineContext startWorkload(SimWorkload workload, Map<String, Object> meta);
/**
- * Cancel the workload that is currently running on this machine.
- *
- * If no workload is active, this operation is a no-op.
+ * Cancel the active workload on this machine (if any).
*/
- public fun cancel()
+ void cancel();
}
diff --git a/opendc-simulator/opendc-simulator-compute/src/main/java/org/opendc/simulator/compute/SimMachineContext.java b/opendc-simulator/opendc-simulator-compute/src/main/java/org/opendc/simulator/compute/SimMachineContext.java
new file mode 100644
index 00000000..f6a3bd38
--- /dev/null
+++ b/opendc-simulator/opendc-simulator-compute/src/main/java/org/opendc/simulator/compute/SimMachineContext.java
@@ -0,0 +1,74 @@
+/*
+ * Copyright (c) 2022 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 java.util.List;
+import java.util.Map;
+import org.opendc.simulator.compute.workload.SimWorkload;
+import org.opendc.simulator.flow2.FlowGraph;
+
+/**
+ * A simulated execution context in which a bootable image runs.
+ *
+ * <p>
+ * This interface represents the interface between the running image (e.g. operating system) and the physical
+ * or virtual firmware on which the image runs.
+ */
+public interface SimMachineContext {
+ /**
+ * Return the {@link FlowGraph} in which the workload executes.
+ */
+ FlowGraph getGraph();
+
+ /**
+ * Return the metadata associated with the context.
+ * <p>
+ * Users can pass this metadata to the workload via {@link SimMachine#startWorkload(SimWorkload, Map)}.
+ */
+ Map<String, Object> getMeta();
+
+ /**
+ * Return the CPUs available on the machine.
+ */
+ List<? extends SimProcessingUnit> getCpus();
+
+ /**
+ * Return the memory interface of the machine.
+ */
+ SimMemory getMemory();
+
+ /**
+ * Return the network interfaces available to the workload.
+ */
+ List<? extends SimNetworkInterface> getNetworkInterfaces();
+
+ /**
+ * Return the storage devices available to the workload.
+ */
+ List<? extends SimStorageInterface> getStorageInterfaces();
+
+ /**
+ * Shutdown the workload.
+ */
+ void shutdown();
+}
diff --git a/opendc-simulator/opendc-simulator-compute/src/main/kotlin/org/opendc/simulator/compute/SimMemory.kt b/opendc-simulator/opendc-simulator-compute/src/main/java/org/opendc/simulator/compute/SimMemory.java
index b1aef495..4fcc64ab 100644
--- a/opendc-simulator/opendc-simulator-compute/src/main/kotlin/org/opendc/simulator/compute/SimMemory.kt
+++ b/opendc-simulator/opendc-simulator-compute/src/main/java/org/opendc/simulator/compute/SimMemory.java
@@ -1,5 +1,5 @@
/*
- * Copyright (c) 2021 AtLarge Research
+ * Copyright (c) 2022 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,17 +20,23 @@
* SOFTWARE.
*/
-package org.opendc.simulator.compute
+package org.opendc.simulator.compute;
-import org.opendc.simulator.compute.model.MemoryUnit
-import org.opendc.simulator.flow.FlowConsumer
+import java.util.List;
+import org.opendc.simulator.compute.model.MemoryUnit;
+import org.opendc.simulator.flow2.sink.FlowSink;
/**
* An interface to control the memory usage of simulated workloads.
*/
-public interface SimMemory : FlowConsumer {
+public interface SimMemory extends FlowSink {
/**
- * The models representing the static information of the memory units supporting this interface.
+ * Return the total capacity of the memory (in MBs).
*/
- public val models: List<MemoryUnit>
+ double getCapacity();
+
+ /**
+ * Return the models representing the static information of the memory units supporting this interface.
+ */
+ List<MemoryUnit> getModels();
}
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/java/org/opendc/simulator/compute/SimNetworkInterface.java
index 660b2871..4b623e59 100644
--- a/opendc-simulator/opendc-simulator-compute/src/main/kotlin/org/opendc/simulator/compute/SimNetworkInterface.kt
+++ b/opendc-simulator/opendc-simulator-compute/src/main/java/org/opendc/simulator/compute/SimNetworkInterface.java
@@ -1,5 +1,5 @@
/*
- * Copyright (c) 2021 AtLarge Research
+ * Copyright (c) 2022 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,32 +20,32 @@
* SOFTWARE.
*/
-package org.opendc.simulator.compute
+package org.opendc.simulator.compute;
-import org.opendc.simulator.flow.FlowConsumer
-import org.opendc.simulator.flow.FlowSource
+import org.opendc.simulator.flow2.Inlet;
+import org.opendc.simulator.flow2.Outlet;
/**
* A firmware interface to a network adapter.
*/
public interface SimNetworkInterface {
/**
- * The name of the network interface.
+ * Return the name of the network interface.
*/
- public val name: String
+ String getName();
/**
- * The unidirectional bandwidth of the network interface in Mbps.
+ * Return the unidirectional bandwidth of the network interface in Mbps.
*/
- public val bandwidth: Double
+ double getBandwidth();
/**
- * The resource provider for the transmit channel of the network interface.
+ * Return the inlet for the "transmit" channel of the network interface.
*/
- public val tx: FlowConsumer
+ Inlet getTx();
/**
- * The resource consumer for the receive channel of the network interface.
+ * Return the outlet for the "receive" channel of the network interface.
*/
- public val rx: FlowSource
+ Outlet getRx();
}
diff --git a/opendc-simulator/opendc-simulator-flow/src/main/kotlin/org/opendc/simulator/flow/FlowConnection.kt b/opendc-simulator/opendc-simulator-compute/src/main/java/org/opendc/simulator/compute/SimProcessingUnit.java
index 8ff0bc76..3dbd3656 100644
--- a/opendc-simulator/opendc-simulator-flow/src/main/kotlin/org/opendc/simulator/flow/FlowConnection.kt
+++ b/opendc-simulator/opendc-simulator-compute/src/main/java/org/opendc/simulator/compute/SimProcessingUnit.java
@@ -1,5 +1,5 @@
/*
- * Copyright (c) 2021 AtLarge Research
+ * Copyright (c) 2022 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,53 +20,43 @@
* SOFTWARE.
*/
-package org.opendc.simulator.flow
+package org.opendc.simulator.compute;
+
+import org.opendc.simulator.compute.model.ProcessingUnit;
+import org.opendc.simulator.flow2.sink.FlowSink;
/**
- * An active connection between a [FlowSource] and [FlowConsumer].
+ * A simulated processing unit.
*/
-public interface FlowConnection : AutoCloseable {
- /**
- * The capacity of the connection.
- */
- public val capacity: Double
-
- /**
- * The flow rate over the connection.
- */
- public val rate: Double
-
+public interface SimProcessingUnit extends FlowSink {
/**
- * The flow demand of the source.
+ * Return the base clock frequency of the processing unit (in MHz).
*/
- public val demand: Double
+ double getFrequency();
/**
- * A flag to control whether [FlowSource.onConverge] should be invoked for this source.
- */
- public var shouldSourceConverge: Boolean
-
- /**
- * Pull the source.
+ * Adjust the base clock frequency of the processing unit.
+ *
+ * <p>
+ * The CPU may or may not round the new frequency to one of its pre-defined frequency steps.
+ *
+ * @param frequency The new frequency to set the clock of the processing unit to.
+ * @throws UnsupportedOperationException if the base clock cannot be adjusted.
*/
- public fun pull()
+ void setFrequency(double frequency);
/**
- * Pull the source.
- *
- * @param now The timestamp at which the connection is pulled.
+ * The demand on the processing unit.
*/
- public fun pull(now: Long)
+ double getDemand();
/**
- * Push the given flow [rate] over this connection.
- *
- * @param rate The rate of the flow to push.
+ * The speed of the processing unit.
*/
- public fun push(rate: Double)
+ double getSpeed();
/**
- * Disconnect the consumer from its source.
+ * The model representing the static properties of the processing unit.
*/
- public override fun close()
+ ProcessingUnit getModel();
}
diff --git a/opendc-simulator/opendc-simulator-compute/src/main/java/org/opendc/simulator/compute/SimPsu.java b/opendc-simulator/opendc-simulator-compute/src/main/java/org/opendc/simulator/compute/SimPsu.java
new file mode 100644
index 00000000..7f1f97a0
--- /dev/null
+++ b/opendc-simulator/opendc-simulator-compute/src/main/java/org/opendc/simulator/compute/SimPsu.java
@@ -0,0 +1,71 @@
+/*
+ * Copyright (c) 2022 AtLarge Research
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
+ * of this software and associated documentation files (the "Software"), to deal
+ * in the Software without restriction, including without limitation the rights
+ * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+ * copies of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included in all
+ * copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
+ * SOFTWARE.
+ */
+
+package org.opendc.simulator.compute;
+
+import org.opendc.simulator.compute.model.ProcessingUnit;
+import org.opendc.simulator.flow2.InPort;
+import org.opendc.simulator.power.SimPowerInlet;
+
+/**
+ * A power supply unit in a {@link SimBareMetalMachine}.
+ *
+ * <p>
+ * This class manages the computation of power usage for a {@link SimBareMetalMachine} based on the resource usage.
+ */
+public abstract class SimPsu extends SimPowerInlet {
+ /**
+ * Return the power demand of the machine (in W) measured in the PSU.
+ * <p>
+ * This method provides access to the power consumption of the machine before PSU losses are applied.
+ */
+ public abstract double getPowerDemand();
+
+ /**
+ * Return the instantaneous power usage of the machine (in W) measured at the inlet of the power supply.
+ */
+ public abstract double getPowerUsage();
+
+ /**
+ * Return the cumulated energy usage of the machine (in J) measured at the inlet of the powers supply.
+ */
+ public abstract double getEnergyUsage();
+
+ /**
+ * Return an {@link InPort} that converts processing demand (in MHz) into energy demand (J) for the specified CPU
+ * <code>model</code>.
+ *
+ * @param id The unique identifier of the CPU for this machine.
+ * @param model The details of the processing unit.
+ */
+ abstract InPort getCpuPower(int id, ProcessingUnit model);
+
+ /**
+ * This method is invoked when the CPU frequency is changed for the specified <code>port</code>.
+ *
+ * @param port The {@link InPort} for which the capacity is changed.
+ * @param capacity The capacity to change to.
+ */
+ void setCpuFrequency(InPort port, double capacity) {
+ port.pull((float) capacity);
+ }
+}
diff --git a/opendc-simulator/opendc-simulator-compute/src/main/java/org/opendc/simulator/compute/SimPsuFactories.java b/opendc-simulator/opendc-simulator-compute/src/main/java/org/opendc/simulator/compute/SimPsuFactories.java
new file mode 100644
index 00000000..52d04052
--- /dev/null
+++ b/opendc-simulator/opendc-simulator-compute/src/main/java/org/opendc/simulator/compute/SimPsuFactories.java
@@ -0,0 +1,214 @@
+/*
+ * Copyright (c) 2022 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 java.time.Clock;
+import org.jetbrains.annotations.NotNull;
+import org.opendc.simulator.compute.model.ProcessingUnit;
+import org.opendc.simulator.compute.power.CpuPowerModel;
+import org.opendc.simulator.flow2.FlowGraph;
+import org.opendc.simulator.flow2.FlowStage;
+import org.opendc.simulator.flow2.FlowStageLogic;
+import org.opendc.simulator.flow2.InHandler;
+import org.opendc.simulator.flow2.InPort;
+import org.opendc.simulator.flow2.OutPort;
+import org.opendc.simulator.flow2.Outlet;
+
+/**
+ * A collection {@link SimPsu} implementations.
+ */
+public class SimPsuFactories {
+ private SimPsuFactories() {}
+
+ /**
+ * Return a {@link SimPsuFactory} of {@link SimPsu} implementations that do not measure any power consumption.
+ *
+ * <p>
+ * This implementation has the lowest performance impact and users are advised to use this factory if they do not
+ * consider power consumption in their experiments.
+ */
+ public static SimPsuFactory noop() {
+ return NoopPsu.FACTORY;
+ }
+
+ /**
+ * Return a {@link SimPsuFactory} of {@link SimPsu} implementations that use a {@link CpuPowerModel} to estimate the
+ * power consumption of a machine based on its CPU utilization.
+ *
+ * @param model The power model to estimate the power consumption based on the CPU usage.
+ */
+ public static SimPsuFactory simple(CpuPowerModel model) {
+ return (machine, graph) -> new SimplePsu(graph, model);
+ }
+
+ /**
+ * A {@link SimPsu} implementation that does not attempt to measure power consumption.
+ */
+ private static final class NoopPsu extends SimPsu implements FlowStageLogic {
+ private static final SimPsuFactory FACTORY = (machine, graph) -> new NoopPsu(graph);
+
+ private final FlowStage stage;
+ private final OutPort out;
+
+ NoopPsu(FlowGraph graph) {
+ stage = graph.newStage(this);
+ out = stage.getOutlet("out");
+ out.setMask(true);
+ }
+
+ @Override
+ public double getPowerDemand() {
+ return 0;
+ }
+
+ @Override
+ public double getPowerUsage() {
+ return 0;
+ }
+
+ @Override
+ public double getEnergyUsage() {
+ return 0;
+ }
+
+ @Override
+ InPort getCpuPower(int id, ProcessingUnit model) {
+ final InPort port = stage.getInlet("cpu" + id);
+ port.setMask(true);
+ return port;
+ }
+
+ @Override
+ public long onUpdate(FlowStage ctx, long now) {
+ return Long.MAX_VALUE;
+ }
+
+ @NotNull
+ @Override
+ public Outlet getFlowOutlet() {
+ return out;
+ }
+ }
+
+ /**
+ * A {@link SimPsu} implementation that estimates the power consumption based on CPU usage.
+ */
+ private static final class SimplePsu extends SimPsu implements FlowStageLogic {
+ private final FlowStage stage;
+ private final OutPort out;
+ private final CpuPowerModel model;
+ private final Clock clock;
+
+ private double targetFreq;
+ private double totalUsage;
+ private long lastUpdate;
+
+ private double powerUsage;
+ private double energyUsage;
+
+ private final InHandler handler = new InHandler() {
+ @Override
+ public void onPush(InPort port, float demand) {
+ totalUsage += -port.getDemand() + demand;
+ }
+
+ @Override
+ public void onUpstreamFinish(InPort port, Throwable cause) {
+ totalUsage -= port.getDemand();
+ }
+ };
+
+ SimplePsu(FlowGraph graph, CpuPowerModel model) {
+ this.stage = graph.newStage(this);
+ this.model = model;
+ this.clock = graph.getEngine().getClock();
+ this.out = stage.getOutlet("out");
+ this.out.setMask(true);
+
+ lastUpdate = graph.getEngine().getClock().millis();
+ }
+
+ @Override
+ public double getPowerDemand() {
+ return totalUsage;
+ }
+
+ @Override
+ public double getPowerUsage() {
+ return powerUsage;
+ }
+
+ @Override
+ public double getEnergyUsage() {
+ updateEnergyUsage(clock.millis());
+ return energyUsage;
+ }
+
+ @Override
+ InPort getCpuPower(int id, ProcessingUnit model) {
+ targetFreq += model.getFrequency();
+
+ final InPort port = stage.getInlet("cpu" + id);
+ port.setHandler(handler);
+ return port;
+ }
+
+ @Override
+ void setCpuFrequency(InPort port, double capacity) {
+ targetFreq += -port.getCapacity() + capacity;
+
+ super.setCpuFrequency(port, capacity);
+ }
+
+ @Override
+ public long onUpdate(FlowStage ctx, long now) {
+ updateEnergyUsage(now);
+
+ double usage = model.computePower(totalUsage / targetFreq);
+ out.push((float) usage);
+ powerUsage = usage;
+
+ return Long.MAX_VALUE;
+ }
+
+ @NotNull
+ @Override
+ public Outlet getFlowOutlet() {
+ return out;
+ }
+
+ /**
+ * Calculate the energy usage up until <code>now</code>.
+ */
+ private void updateEnergyUsage(long now) {
+ long lastUpdate = this.lastUpdate;
+ this.lastUpdate = now;
+
+ long duration = now - lastUpdate;
+ if (duration > 0) {
+ // Compute the energy usage of the machine
+ energyUsage += powerUsage * duration * 0.001;
+ }
+ }
+ }
+}
diff --git a/opendc-simulator/opendc-simulator-flow/src/main/kotlin/org/opendc/simulator/flow/FlowConvergenceListener.kt b/opendc-simulator/opendc-simulator-compute/src/main/java/org/opendc/simulator/compute/SimPsuFactory.java
index 62cb10d1..872e7016 100644
--- a/opendc-simulator/opendc-simulator-flow/src/main/kotlin/org/opendc/simulator/flow/FlowConvergenceListener.kt
+++ b/opendc-simulator/opendc-simulator-compute/src/main/java/org/opendc/simulator/compute/SimPsuFactory.java
@@ -1,5 +1,5 @@
/*
- * Copyright (c) 2021 AtLarge Research
+ * Copyright (c) 2022 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,16 +20,19 @@
* SOFTWARE.
*/
-package org.opendc.simulator.flow
+package org.opendc.simulator.compute;
+
+import org.opendc.simulator.flow2.FlowGraph;
/**
- * A listener interface for when a flow stage has converged into a steady-state.
+ * A factory interface for {@link SimPsu} implementations.
*/
-public interface FlowConvergenceListener {
+public interface SimPsuFactory {
/**
- * This method is invoked when the system has converged to a steady-state.
+ * Construct a new {@link SimPsu} for the specified <code>machine</code>.
*
- * @param now The timestamp at which the system converged.
+ * @param machine The machine to construct the power supply for.
+ * @param graph The {@link FlowGraph} used for the simulation.
*/
- public fun onConverge(now: Long) {}
+ SimPsu newPsu(SimMachine machine, FlowGraph graph);
}
diff --git a/opendc-simulator/opendc-simulator-compute/src/main/kotlin/org/opendc/simulator/compute/SimStorageInterface.kt b/opendc-simulator/opendc-simulator-compute/src/main/java/org/opendc/simulator/compute/SimStorageInterface.java
index 3d648671..341122dc 100644
--- a/opendc-simulator/opendc-simulator-compute/src/main/kotlin/org/opendc/simulator/compute/SimStorageInterface.kt
+++ b/opendc-simulator/opendc-simulator-compute/src/main/java/org/opendc/simulator/compute/SimStorageInterface.java
@@ -1,5 +1,5 @@
/*
- * Copyright (c) 2021 AtLarge Research
+ * Copyright (c) 2022 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,31 +20,31 @@
* SOFTWARE.
*/
-package org.opendc.simulator.compute
+package org.opendc.simulator.compute;
-import org.opendc.simulator.flow.FlowConsumer
+import org.opendc.simulator.flow2.Inlet;
/**
* A firmware interface to a storage device.
*/
public interface SimStorageInterface {
/**
- * The name of the storage device.
+ * Return the name of the network interface.
*/
- public val name: String
+ String getName();
/**
- * The capacity of the storage device in MBs.
+ * Return the capacity of the storage device in MBs.
*/
- public val capacity: Double
+ double getCapacity();
/**
- * The resource provider for the read operations of the storage device.
+ * Return the inlet for the read operations of the storage device.
*/
- public val read: FlowConsumer
+ Inlet getRead();
/**
- * The resource consumer for the write operation of the storage device.
+ * Return the inlet for the write operation of the storage device.
*/
- public val write: FlowConsumer
+ Inlet getWrite();
}
diff --git a/opendc-simulator/opendc-simulator-compute/src/main/kotlin/org/opendc/simulator/compute/device/SimNetworkAdapter.kt b/opendc-simulator/opendc-simulator-compute/src/main/java/org/opendc/simulator/compute/device/SimNetworkAdapter.java
index dfb4ecf3..1c16ceff 100644
--- a/opendc-simulator/opendc-simulator-compute/src/main/kotlin/org/opendc/simulator/compute/device/SimNetworkAdapter.kt
+++ b/opendc-simulator/opendc-simulator-compute/src/main/java/org/opendc/simulator/compute/device/SimNetworkAdapter.java
@@ -1,5 +1,5 @@
/*
- * Copyright (c) 2021 AtLarge Research
+ * Copyright (c) 2022 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,17 +20,17 @@
* SOFTWARE.
*/
-package org.opendc.simulator.compute.device
+package org.opendc.simulator.compute.device;
-import org.opendc.simulator.compute.SimMachine
-import org.opendc.simulator.network.SimNetworkPort
+import org.opendc.simulator.compute.SimMachine;
+import org.opendc.simulator.network.SimNetworkPort;
/**
- * A simulated network interface card (NIC or network adapter) that can be attached to a [SimMachine].
+ * A simulated network interface card (NIC or network adapter) that can be attached to a {@link SimMachine}.
*/
-public abstract class SimNetworkAdapter : SimNetworkPort(), SimPeripheral {
+public abstract class SimNetworkAdapter extends SimNetworkPort implements SimPeripheral {
/**
- * The unidirectional bandwidth of the network adapter in Mbps.
+ * Return the unidirectional bandwidth of the network adapter (in Mbps).
*/
- public abstract val bandwidth: Double
+ public abstract double getBandwidth();
}
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/java/org/opendc/simulator/compute/device/SimPeripheral.java
index 268271be..40bd268b 100644
--- a/opendc-simulator/opendc-simulator-compute/src/main/kotlin/org/opendc/simulator/compute/device/SimPeripheral.kt
+++ b/opendc-simulator/opendc-simulator-compute/src/main/java/org/opendc/simulator/compute/device/SimPeripheral.java
@@ -1,5 +1,5 @@
/*
- * Copyright (c) 2021 AtLarge Research
+ * Copyright (c) 2022 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.compute.device
+package org.opendc.simulator.compute.device;
-import org.opendc.simulator.compute.SimMachine
+import org.opendc.simulator.compute.SimMachine;
/**
- * A component that can be attached to a [SimMachine].
- *
+ * A component that can be attached to a {@link SimMachine}.
+ * <p>
* This interface represents the physical view of the peripheral and should be used to configure the physical properties
* of the peripheral.
*/
-public interface SimPeripheral
+public interface SimPeripheral {}
diff --git a/opendc-simulator/opendc-simulator-compute/src/main/java/org/opendc/simulator/compute/kernel/SimHypervisor.java b/opendc-simulator/opendc-simulator-compute/src/main/java/org/opendc/simulator/compute/kernel/SimHypervisor.java
new file mode 100644
index 00000000..6e295837
--- /dev/null
+++ b/opendc-simulator/opendc-simulator-compute/src/main/java/org/opendc/simulator/compute/kernel/SimHypervisor.java
@@ -0,0 +1,911 @@
+/*
+ * Copyright (c) 2022 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 java.time.Clock;
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.List;
+import java.util.Map;
+import java.util.SplittableRandom;
+import java.util.stream.Collectors;
+import org.opendc.simulator.compute.SimAbstractMachine;
+import org.opendc.simulator.compute.SimMachine;
+import org.opendc.simulator.compute.SimMachineContext;
+import org.opendc.simulator.compute.SimMemory;
+import org.opendc.simulator.compute.SimNetworkInterface;
+import org.opendc.simulator.compute.SimProcessingUnit;
+import org.opendc.simulator.compute.SimStorageInterface;
+import org.opendc.simulator.compute.device.SimPeripheral;
+import org.opendc.simulator.compute.kernel.cpufreq.ScalingGovernor;
+import org.opendc.simulator.compute.kernel.cpufreq.ScalingGovernorFactory;
+import org.opendc.simulator.compute.kernel.cpufreq.ScalingPolicy;
+import org.opendc.simulator.compute.kernel.interference.VmInterferenceDomain;
+import org.opendc.simulator.compute.kernel.interference.VmInterferenceMember;
+import org.opendc.simulator.compute.kernel.interference.VmInterferenceProfile;
+import org.opendc.simulator.compute.model.MachineModel;
+import org.opendc.simulator.compute.model.ProcessingUnit;
+import org.opendc.simulator.compute.workload.SimWorkload;
+import org.opendc.simulator.flow2.FlowGraph;
+import org.opendc.simulator.flow2.FlowStage;
+import org.opendc.simulator.flow2.FlowStageLogic;
+import org.opendc.simulator.flow2.InHandler;
+import org.opendc.simulator.flow2.InPort;
+import org.opendc.simulator.flow2.Inlet;
+import org.opendc.simulator.flow2.OutHandler;
+import org.opendc.simulator.flow2.OutPort;
+import org.opendc.simulator.flow2.mux.FlowMultiplexer;
+import org.opendc.simulator.flow2.mux.FlowMultiplexerFactory;
+
+/**
+ * A SimHypervisor facilitates the execution of multiple concurrent {@link SimWorkload}s, while acting as a single
+ * workload to another {@link SimMachine}.
+ */
+public final class SimHypervisor implements SimWorkload {
+ private final FlowMultiplexerFactory muxFactory;
+ private final SplittableRandom random;
+ private final ScalingGovernorFactory scalingGovernorFactory;
+ private final VmInterferenceDomain interferenceDomain;
+
+ private Context activeContext;
+ private final ArrayList<VirtualMachine> vms = new ArrayList<>();
+ private final HvCounters counters = new HvCounters();
+
+ /**
+ * Construct a {@link SimHypervisor} instance.
+ *
+ * @param muxFactory The factory for the {@link FlowMultiplexer} to multiplex the workloads.
+ * @param random A randomness generator for the interference calculations.
+ * @param scalingGovernorFactory The factory for the scaling governor to use for scaling the CPU frequency.
+ * @param interferenceDomain The interference domain to which the hypervisor belongs.
+ */
+ private SimHypervisor(
+ FlowMultiplexerFactory muxFactory,
+ SplittableRandom random,
+ ScalingGovernorFactory scalingGovernorFactory,
+ VmInterferenceDomain interferenceDomain) {
+ this.muxFactory = muxFactory;
+ this.random = random;
+ this.scalingGovernorFactory = scalingGovernorFactory;
+ this.interferenceDomain = interferenceDomain;
+ }
+
+ /**
+ * Create a {@link SimHypervisor} instance.
+ *
+ * @param muxFactory The factory for the {@link FlowMultiplexer} to multiplex the workloads.
+ * @param random A randomness generator for the interference calculations.
+ * @param scalingGovernorFactory The factory for the scaling governor to use for scaling the CPU frequency.
+ * @param interferenceDomain The interference domain to which the hypervisor belongs.
+ */
+ public static SimHypervisor create(
+ FlowMultiplexerFactory muxFactory,
+ SplittableRandom random,
+ ScalingGovernorFactory scalingGovernorFactory,
+ VmInterferenceDomain interferenceDomain) {
+ return new SimHypervisor(muxFactory, random, scalingGovernorFactory, interferenceDomain);
+ }
+
+ /**
+ * Create a {@link SimHypervisor} instance with a default interference domain.
+ *
+ * @param muxFactory The factory for the {@link FlowMultiplexer} to multiplex the workloads.
+ * @param random A randomness generator for the interference calculations.
+ * @param scalingGovernorFactory The factory for the scaling governor to use for scaling the CPU frequency.
+ */
+ public static SimHypervisor create(
+ FlowMultiplexerFactory muxFactory, SplittableRandom random, ScalingGovernorFactory scalingGovernorFactory) {
+ return create(muxFactory, random, scalingGovernorFactory, new VmInterferenceDomain());
+ }
+
+ /**
+ * Create a {@link SimHypervisor} instance with a default interference domain and scaling governor.
+ *
+ * @param muxFactory The factory for the {@link FlowMultiplexer} to multiplex the workloads.
+ * @param random A randomness generator for the interference calculations.
+ */
+ public static SimHypervisor create(FlowMultiplexerFactory muxFactory, SplittableRandom random) {
+ return create(muxFactory, random, null);
+ }
+
+ /**
+ * Return the performance counters of the hypervisor.
+ */
+ public SimHypervisorCounters getCounters() {
+ return counters;
+ }
+
+ /**
+ * Return the virtual machines running on this hypervisor.
+ */
+ public List<? extends SimVirtualMachine> getVirtualMachines() {
+ return Collections.unmodifiableList(vms);
+ }
+
+ /**
+ * Create a {@link SimVirtualMachine} instance on which users may run a [SimWorkload].
+ *
+ * @param model The machine to create.
+ */
+ public SimVirtualMachine newMachine(MachineModel model) {
+ if (!canFit(model)) {
+ throw new IllegalArgumentException("Machine does not fit");
+ }
+
+ VirtualMachine vm = new VirtualMachine(model);
+ vms.add(vm);
+ return vm;
+ }
+
+ /**
+ * Remove the specified <code>machine</code> from the hypervisor.
+ *
+ * @param machine The machine to remove.
+ */
+ public void removeMachine(SimVirtualMachine machine) {
+ if (vms.remove(machine)) {
+ // This cast must always succeed, since `_vms` only contains `VirtualMachine` types.
+ ((VirtualMachine) machine).close();
+ }
+ }
+
+ /**
+ * Return the CPU capacity of the hypervisor in MHz.
+ */
+ public double getCpuCapacity() {
+ final Context context = activeContext;
+
+ if (context == null) {
+ return 0.0;
+ }
+
+ return context.previousCapacity;
+ }
+
+ /**
+ * The CPU demand of the hypervisor in MHz.
+ */
+ public double getCpuDemand() {
+ final Context context = activeContext;
+
+ if (context == null) {
+ return 0.0;
+ }
+
+ return context.previousDemand;
+ }
+
+ /**
+ * The CPU usage of the hypervisor in MHz.
+ */
+ public double getCpuUsage() {
+ final Context context = activeContext;
+
+ if (context == null) {
+ return 0.0;
+ }
+
+ return context.previousRate;
+ }
+
+ /**
+ * Determine whether the specified machine characterized by <code>model</code> can fit on this hypervisor at this
+ * moment.
+ */
+ public boolean canFit(MachineModel model) {
+ final Context context = activeContext;
+ if (context == null) {
+ return false;
+ }
+
+ final FlowMultiplexer multiplexer = context.multiplexer;
+ return (multiplexer.getMaxInputs() - multiplexer.getInputCount())
+ >= model.getCpus().size();
+ }
+
+ @Override
+ public void onStart(SimMachineContext ctx) {
+ final Context context = new Context(ctx, muxFactory, scalingGovernorFactory, counters);
+ context.start();
+ activeContext = context;
+ }
+
+ @Override
+ public void onStop(SimMachineContext ctx) {
+ final Context context = activeContext;
+ if (context != null) {
+ activeContext = null;
+ context.stop();
+ }
+ }
+
+ /**
+ * The context which carries the state when the hypervisor is running on a machine.
+ */
+ private static final class Context implements FlowStageLogic {
+ private final SimMachineContext ctx;
+ private final FlowMultiplexer multiplexer;
+ private final FlowStage stage;
+ private final List<ScalingGovernor> scalingGovernors;
+ private final Clock clock;
+ private final HvCounters counters;
+
+ private long lastCounterUpdate;
+ private final double d;
+ private float previousDemand;
+ private float previousRate;
+ private float previousCapacity;
+
+ private Context(
+ SimMachineContext ctx,
+ FlowMultiplexerFactory muxFactory,
+ ScalingGovernorFactory scalingGovernorFactory,
+ HvCounters counters) {
+
+ this.ctx = ctx;
+ this.counters = counters;
+
+ final FlowGraph graph = ctx.getGraph();
+ this.multiplexer = muxFactory.newMultiplexer(graph);
+ this.stage = graph.newStage(this);
+ this.clock = graph.getEngine().getClock();
+
+ this.lastCounterUpdate = clock.millis();
+
+ if (scalingGovernorFactory != null) {
+ this.scalingGovernors = ctx.getCpus().stream()
+ .map(cpu -> scalingGovernorFactory.newGovernor(new ScalingPolicyImpl(cpu)))
+ .collect(Collectors.toList());
+ } else {
+ this.scalingGovernors = Collections.emptyList();
+ }
+
+ float cpuCapacity = 0.f;
+ final List<? extends SimProcessingUnit> cpus = ctx.getCpus();
+ for (SimProcessingUnit cpu : cpus) {
+ cpuCapacity += cpu.getFrequency();
+ }
+ this.d = cpus.size() / cpuCapacity;
+ }
+
+ /**
+ * Start the hypervisor on a new machine.
+ */
+ void start() {
+ final FlowGraph graph = ctx.getGraph();
+ final FlowMultiplexer multiplexer = this.multiplexer;
+
+ for (SimProcessingUnit cpu : ctx.getCpus()) {
+ graph.connect(multiplexer.newOutput(), cpu.getInput());
+ }
+
+ for (ScalingGovernor governor : scalingGovernors) {
+ governor.onStart();
+ }
+ }
+
+ /**
+ * Stop the hypervisor.
+ */
+ void stop() {
+ // Synchronize the counters before stopping the hypervisor. Otherwise, the last report is missed.
+ updateCounters(clock.millis());
+
+ stage.close();
+ }
+
+ /**
+ * Invalidate the {@link FlowStage} of the hypervisor.
+ */
+ void invalidate() {
+ stage.invalidate();
+ }
+
+ /**
+ * Update the performance counters of the hypervisor.
+ *
+ * @param now The timestamp at which to update the counter.
+ */
+ void updateCounters(long now) {
+ long lastUpdate = this.lastCounterUpdate;
+ this.lastCounterUpdate = now;
+ long delta = now - lastUpdate;
+
+ if (delta > 0) {
+ final HvCounters counters = this.counters;
+
+ float demand = previousDemand;
+ float rate = previousRate;
+ float capacity = previousCapacity;
+
+ final double factor = this.d * delta;
+
+ counters.cpuActiveTime += Math.round(rate * factor);
+ counters.cpuIdleTime += Math.round((capacity - rate) * factor);
+ counters.cpuStealTime += Math.round((demand - rate) * factor);
+ }
+ }
+
+ /**
+ * Update the performance counters of the hypervisor.
+ */
+ void updateCounters() {
+ updateCounters(clock.millis());
+ }
+
+ @Override
+ public long onUpdate(FlowStage ctx, long now) {
+ updateCounters(now);
+
+ final FlowMultiplexer multiplexer = this.multiplexer;
+ final List<ScalingGovernor> scalingGovernors = this.scalingGovernors;
+
+ float demand = multiplexer.getDemand();
+ float rate = multiplexer.getRate();
+ float capacity = multiplexer.getCapacity();
+
+ this.previousDemand = demand;
+ this.previousRate = rate;
+ this.previousCapacity = capacity;
+
+ double load = rate / Math.min(1.0, capacity);
+
+ if (!scalingGovernors.isEmpty()) {
+ for (ScalingGovernor governor : scalingGovernors) {
+ governor.onLimit(load);
+ }
+ }
+
+ return Long.MAX_VALUE;
+ }
+ }
+
+ /**
+ * A {@link ScalingPolicy} for a physical CPU of the hypervisor.
+ */
+ private static final class ScalingPolicyImpl implements ScalingPolicy {
+ private final SimProcessingUnit cpu;
+
+ private ScalingPolicyImpl(SimProcessingUnit cpu) {
+ this.cpu = cpu;
+ }
+
+ @Override
+ public SimProcessingUnit getCpu() {
+ return cpu;
+ }
+
+ @Override
+ public double getTarget() {
+ return cpu.getFrequency();
+ }
+
+ @Override
+ public void setTarget(double target) {
+ cpu.setFrequency(target);
+ }
+
+ @Override
+ public double getMin() {
+ return 0;
+ }
+
+ @Override
+ public double getMax() {
+ return cpu.getModel().getFrequency();
+ }
+ }
+
+ /**
+ * A virtual machine running on the hypervisor.
+ */
+ private class VirtualMachine extends SimAbstractMachine implements SimVirtualMachine {
+ private boolean isClosed;
+ private final VmCounters counters = new VmCounters(this);
+
+ private VirtualMachine(MachineModel model) {
+ super(model);
+ }
+
+ @Override
+ public SimHypervisorCounters getCounters() {
+ return counters;
+ }
+
+ @Override
+ public double getCpuDemand() {
+ final VmContext context = (VmContext) getActiveContext();
+
+ if (context == null) {
+ return 0.0;
+ }
+
+ return context.previousDemand;
+ }
+
+ @Override
+ public double getCpuUsage() {
+ final VmContext context = (VmContext) getActiveContext();
+
+ if (context == null) {
+ return 0.0;
+ }
+
+ return context.usage;
+ }
+
+ @Override
+ public double getCpuCapacity() {
+ final VmContext context = (VmContext) getActiveContext();
+
+ if (context == null) {
+ return 0.0;
+ }
+
+ return context.previousCapacity;
+ }
+
+ @Override
+ public List<? extends SimPeripheral> getPeripherals() {
+ return Collections.emptyList();
+ }
+
+ @Override
+ protected Context createContext(SimWorkload workload, Map<String, Object> meta) {
+ if (isClosed) {
+ throw new IllegalStateException("Virtual machine does not exist anymore");
+ }
+
+ final SimHypervisor.Context context = activeContext;
+ if (context == null) {
+ throw new IllegalStateException("Hypervisor is inactive");
+ }
+
+ return new VmContext(
+ context, this, random, interferenceDomain, counters, SimHypervisor.this.counters, workload, meta);
+ }
+
+ @Override
+ public Context getActiveContext() {
+ return super.getActiveContext();
+ }
+
+ void close() {
+ if (isClosed) {
+ return;
+ }
+
+ isClosed = true;
+ cancel();
+ }
+ }
+
+ /**
+ * A {@link SimAbstractMachine.Context} for a virtual machine instance.
+ */
+ private static final class VmContext extends SimAbstractMachine.Context implements FlowStageLogic {
+ private final Context context;
+ private final SplittableRandom random;
+ private final VmCounters vmCounters;
+ private final HvCounters hvCounters;
+ private final VmInterferenceMember interferenceMember;
+ private final FlowStage stage;
+ private final FlowMultiplexer multiplexer;
+ private final Clock clock;
+
+ private final List<VCpu> cpus;
+ private final SimAbstractMachine.Memory memory;
+ private final List<SimAbstractMachine.NetworkAdapter> net;
+ private final List<SimAbstractMachine.StorageDevice> disk;
+
+ private final Inlet[] muxInlets;
+ private long lastUpdate;
+ private long lastCounterUpdate;
+ private final double d;
+
+ private float demand;
+ private float usage;
+ private float capacity;
+
+ private float previousDemand;
+ private float previousCapacity;
+
+ private VmContext(
+ Context context,
+ VirtualMachine machine,
+ SplittableRandom random,
+ VmInterferenceDomain interferenceDomain,
+ VmCounters vmCounters,
+ HvCounters hvCounters,
+ SimWorkload workload,
+ Map<String, Object> meta) {
+ super(machine, workload, meta);
+
+ this.context = context;
+ this.random = random;
+ this.vmCounters = vmCounters;
+ this.hvCounters = hvCounters;
+ this.clock = context.clock;
+
+ final VmInterferenceProfile interferenceProfile = (VmInterferenceProfile) meta.get("interference-profile");
+ VmInterferenceMember interferenceMember = null;
+ if (interferenceDomain != null && interferenceProfile != null) {
+ interferenceMember = interferenceDomain.join(interferenceProfile);
+ interferenceMember.activate();
+ }
+ this.interferenceMember = interferenceMember;
+
+ final FlowGraph graph = context.ctx.getGraph();
+ final FlowStage stage = graph.newStage(this);
+ this.stage = stage;
+ this.lastUpdate = clock.millis();
+ this.lastCounterUpdate = clock.millis();
+
+ final FlowMultiplexer multiplexer = context.multiplexer;
+ this.multiplexer = multiplexer;
+
+ final MachineModel model = machine.getModel();
+ final List<ProcessingUnit> cpuModels = model.getCpus();
+ final Inlet[] muxInlets = new Inlet[cpuModels.size()];
+ final ArrayList<VCpu> cpus = new ArrayList<>();
+
+ this.muxInlets = muxInlets;
+ this.cpus = cpus;
+
+ float capacity = 0.f;
+
+ for (int i = 0; i < cpuModels.size(); i++) {
+ final Inlet muxInlet = multiplexer.newInput();
+ muxInlets[i] = muxInlet;
+
+ final InPort input = stage.getInlet("cpu" + i);
+ final OutPort output = stage.getOutlet("mux" + i);
+
+ final Handler handler = new Handler(this, input, output);
+ input.setHandler(handler);
+ output.setHandler(handler);
+
+ final ProcessingUnit cpuModel = cpuModels.get(i);
+ capacity += cpuModel.getFrequency();
+
+ final VCpu cpu = new VCpu(cpuModel, input);
+ cpus.add(cpu);
+
+ graph.connect(output, muxInlet);
+ }
+ this.d = cpuModels.size() / capacity;
+
+ this.memory = new SimAbstractMachine.Memory(graph, model.getMemory());
+
+ int netIndex = 0;
+ final ArrayList<SimAbstractMachine.NetworkAdapter> net = new ArrayList<>();
+ this.net = net;
+ for (org.opendc.simulator.compute.model.NetworkAdapter adapter : model.getNetwork()) {
+ net.add(new SimAbstractMachine.NetworkAdapter(graph, adapter, netIndex++));
+ }
+
+ int diskIndex = 0;
+ final ArrayList<SimAbstractMachine.StorageDevice> disk = new ArrayList<>();
+ this.disk = disk;
+ for (org.opendc.simulator.compute.model.StorageDevice device : model.getStorage()) {
+ disk.add(new SimAbstractMachine.StorageDevice(graph, device, diskIndex++));
+ }
+ }
+
+ /**
+ * Update the performance counters of the virtual machine.
+ *
+ * @param now The timestamp at which to update the counter.
+ */
+ void updateCounters(long now) {
+ long lastUpdate = this.lastCounterUpdate;
+ this.lastCounterUpdate = now;
+ long delta = now - lastUpdate;
+
+ if (delta > 0) {
+ final VmCounters counters = this.vmCounters;
+
+ float demand = this.previousDemand;
+ float rate = this.usage;
+ float capacity = this.previousCapacity;
+
+ final double factor = this.d * delta;
+ final double active = rate * factor;
+
+ counters.cpuActiveTime += Math.round(active);
+ counters.cpuIdleTime += Math.round((capacity - rate) * factor);
+ counters.cpuStealTime += Math.round((demand - rate) * factor);
+ }
+ }
+
+ /**
+ * Update the performance counters of the virtual machine.
+ */
+ void updateCounters() {
+ updateCounters(clock.millis());
+ }
+
+ @Override
+ public FlowGraph getGraph() {
+ return stage.getGraph();
+ }
+
+ @Override
+ public List<? extends SimProcessingUnit> getCpus() {
+ return cpus;
+ }
+
+ @Override
+ public SimMemory getMemory() {
+ return memory;
+ }
+
+ @Override
+ public List<? extends SimNetworkInterface> getNetworkInterfaces() {
+ return net;
+ }
+
+ @Override
+ public List<? extends SimStorageInterface> getStorageInterfaces() {
+ return disk;
+ }
+
+ @Override
+ public long onUpdate(FlowStage ctx, long now) {
+ float usage = 0.f;
+ for (Inlet inlet : muxInlets) {
+ usage += ((InPort) inlet).getRate();
+ }
+ this.usage = usage;
+ this.previousDemand = demand;
+ this.previousCapacity = capacity;
+
+ long lastUpdate = this.lastUpdate;
+ this.lastUpdate = now;
+ long delta = now - lastUpdate;
+
+ if (delta > 0) {
+ final VmInterferenceMember interferenceMember = this.interferenceMember;
+ double penalty = 0.0;
+
+ if (interferenceMember != null) {
+ final FlowMultiplexer multiplexer = this.multiplexer;
+ double load = multiplexer.getRate() / Math.min(1.0, multiplexer.getCapacity());
+ penalty = 1 - interferenceMember.apply(random, load);
+ }
+
+ final double factor = this.d * delta;
+ final long lostTime = Math.round(factor * usage * penalty);
+
+ this.vmCounters.cpuLostTime += lostTime;
+ this.hvCounters.cpuLostTime += lostTime;
+ }
+
+ // Invalidate the FlowStage of the hypervisor to update its counters (via onUpdate)
+ context.invalidate();
+
+ return Long.MAX_VALUE;
+ }
+
+ @Override
+ protected void doCancel() {
+ super.doCancel();
+
+ // Synchronize the counters before stopping the hypervisor. Otherwise, the last report is missed.
+ updateCounters(clock.millis());
+
+ stage.close();
+
+ final FlowMultiplexer multiplexer = this.multiplexer;
+ for (Inlet muxInlet : muxInlets) {
+ multiplexer.releaseInput(muxInlet);
+ }
+
+ final VmInterferenceMember interferenceMember = this.interferenceMember;
+ if (interferenceMember != null) {
+ interferenceMember.deactivate();
+ }
+ }
+ }
+
+ /**
+ * A {@link SimProcessingUnit} of a virtual machine.
+ */
+ private static final class VCpu implements SimProcessingUnit {
+ private final ProcessingUnit model;
+ private final InPort input;
+
+ private VCpu(ProcessingUnit model, InPort input) {
+ this.model = model;
+ this.input = input;
+
+ input.pull((float) model.getFrequency());
+ }
+
+ @Override
+ public double getFrequency() {
+ return input.getCapacity();
+ }
+
+ @Override
+ public void setFrequency(double frequency) {
+ input.pull((float) frequency);
+ }
+
+ @Override
+ public double getDemand() {
+ return input.getDemand();
+ }
+
+ @Override
+ public double getSpeed() {
+ return input.getRate();
+ }
+
+ @Override
+ public ProcessingUnit getModel() {
+ return model;
+ }
+
+ @Override
+ public Inlet getInput() {
+ return input;
+ }
+
+ @Override
+ public String toString() {
+ return "SimHypervisor.VCpu[model" + model + "]";
+ }
+ }
+
+ /**
+ * A handler for forwarding flow between an inlet and outlet.
+ */
+ private static class Handler implements InHandler, OutHandler {
+ private final InPort input;
+ private final OutPort output;
+ private final VmContext context;
+
+ private Handler(VmContext context, InPort input, OutPort output) {
+ this.context = context;
+ this.input = input;
+ this.output = output;
+ }
+
+ @Override
+ public void onPush(InPort port, float demand) {
+ context.demand += -port.getDemand() + demand;
+
+ output.push(demand);
+ }
+
+ @Override
+ public void onUpstreamFinish(InPort port, Throwable cause) {
+ context.demand -= port.getDemand();
+
+ output.push(0.f);
+ }
+
+ @Override
+ public float getRate(InPort port) {
+ return output.getRate();
+ }
+
+ @Override
+ public void onPull(OutPort port, float capacity) {
+ context.capacity += -port.getCapacity() + capacity;
+
+ input.pull(capacity);
+ }
+
+ @Override
+ public void onDownstreamFinish(OutPort port, Throwable cause) {
+ context.capacity -= port.getCapacity();
+
+ input.pull(0.f);
+ }
+ }
+
+ /**
+ * Implementation of {@link SimHypervisorCounters} for the hypervisor.
+ */
+ private class HvCounters implements SimHypervisorCounters {
+ private long cpuActiveTime;
+ private long cpuIdleTime;
+ private long cpuStealTime;
+ private long cpuLostTime;
+
+ @Override
+ public long getCpuActiveTime() {
+ return cpuActiveTime;
+ }
+
+ @Override
+ public long getCpuIdleTime() {
+ return cpuIdleTime;
+ }
+
+ @Override
+ public long getCpuStealTime() {
+ return cpuStealTime;
+ }
+
+ @Override
+ public long getCpuLostTime() {
+ return cpuLostTime;
+ }
+
+ @Override
+ public void sync() {
+ final Context context = activeContext;
+
+ if (context != null) {
+ context.updateCounters();
+ }
+ }
+ }
+
+ /**
+ * Implementation of {@link SimHypervisorCounters} for the virtual machine.
+ */
+ private static class VmCounters implements SimHypervisorCounters {
+ private final VirtualMachine vm;
+ private long cpuActiveTime;
+ private long cpuIdleTime;
+ private long cpuStealTime;
+ private long cpuLostTime;
+
+ private VmCounters(VirtualMachine vm) {
+ this.vm = vm;
+ }
+
+ @Override
+ public long getCpuActiveTime() {
+ return cpuActiveTime;
+ }
+
+ @Override
+ public long getCpuIdleTime() {
+ return cpuIdleTime;
+ }
+
+ @Override
+ public long getCpuStealTime() {
+ return cpuStealTime;
+ }
+
+ @Override
+ public long getCpuLostTime() {
+ return cpuLostTime;
+ }
+
+ @Override
+ public void sync() {
+ final VmContext context = (VmContext) vm.getActiveContext();
+
+ if (context != null) {
+ context.updateCounters();
+ }
+ }
+ }
+}
diff --git a/opendc-simulator/opendc-simulator-compute/src/main/kotlin/org/opendc/simulator/compute/kernel/SimHypervisorCounters.kt b/opendc-simulator/opendc-simulator-compute/src/main/java/org/opendc/simulator/compute/kernel/SimHypervisorCounters.java
index 63fee507..fc77e9d6 100644
--- a/opendc-simulator/opendc-simulator-compute/src/main/kotlin/org/opendc/simulator/compute/kernel/SimHypervisorCounters.kt
+++ b/opendc-simulator/opendc-simulator-compute/src/main/java/org/opendc/simulator/compute/kernel/SimHypervisorCounters.java
@@ -1,5 +1,5 @@
/*
- * Copyright (c) 2021 AtLarge Research
+ * Copyright (c) 2022 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,34 +20,34 @@
* SOFTWARE.
*/
-package org.opendc.simulator.compute.kernel
+package org.opendc.simulator.compute.kernel;
/**
- * Performance counters of a [SimHypervisor].
+ * Performance counters of a {@link SimHypervisor}.
*/
public interface SimHypervisorCounters {
/**
- * The amount of time (in milliseconds) the CPUs of the hypervisor were actively running.
+ * Return the amount of time (in milliseconds) the CPUs of the hypervisor were actively running.
*/
- public val cpuActiveTime: Long
+ long getCpuActiveTime();
/**
- * The amount of time (in milliseconds) the CPUs of the hypervisor were idle.
+ * Return the amount of time (in milliseconds) the CPUs of the hypervisor were idle.
*/
- public val cpuIdleTime: Long
+ long getCpuIdleTime();
/**
- * The amount of CPU time (in milliseconds) that virtual machines were ready to run, but were not able to.
+ * Return the amount of CPU time (in milliseconds) that virtual machines were ready to run, but were not able to.
*/
- public val cpuStealTime: Long
+ long getCpuStealTime();
/**
- * The amount of CPU time (in milliseconds) that was lost due to interference between virtual machines.
+ * Return the amount of CPU time (in milliseconds) that was lost due to interference between virtual machines.
*/
- public val cpuLostTime: Long
+ long getCpuLostTime();
/**
- * Flush the counter values.
+ * Synchronize the counter values.
*/
- public fun flush()
+ void sync();
}
diff --git a/opendc-simulator/opendc-simulator-compute/src/main/kotlin/org/opendc/simulator/compute/kernel/SimVirtualMachine.kt b/opendc-simulator/opendc-simulator-compute/src/main/java/org/opendc/simulator/compute/kernel/SimVirtualMachine.java
index 36219ef2..fdf5e47f 100644
--- a/opendc-simulator/opendc-simulator-compute/src/main/kotlin/org/opendc/simulator/compute/kernel/SimVirtualMachine.kt
+++ b/opendc-simulator/opendc-simulator-compute/src/main/java/org/opendc/simulator/compute/kernel/SimVirtualMachine.java
@@ -1,5 +1,5 @@
/*
- * Copyright (c) 2021 AtLarge Research
+ * Copyright (c) 2022 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,31 +20,31 @@
* SOFTWARE.
*/
-package org.opendc.simulator.compute.kernel
+package org.opendc.simulator.compute.kernel;
-import org.opendc.simulator.compute.SimMachine
+import org.opendc.simulator.compute.SimMachine;
/**
- * A virtual [SimMachine] running on top of another [SimMachine].
+ * A virtual {@link SimMachine} running on top of another {@link SimMachine}.
*/
-public interface SimVirtualMachine : SimMachine {
+public interface SimVirtualMachine extends SimMachine {
/**
- * The resource counters associated with the virtual machine.
+ * Return the performance counters associated with the virtual machine.
*/
- public val counters: SimHypervisorCounters
+ SimHypervisorCounters getCounters();
/**
- * The CPU usage of the VM in MHz.
+ * Return the CPU usage of the VM in MHz.
*/
- public val cpuUsage: Double
+ double getCpuUsage();
/**
- * The CPU usage of the VM in MHz.
+ * Return the CPU usage of the VM in MHz.
*/
- public val cpuDemand: Double
+ double getCpuDemand();
/**
- * The CPU capacity of the VM in MHz.
+ * Return the CPU capacity of the VM in MHz.
*/
- public val cpuCapacity: Double
+ double getCpuCapacity();
}
diff --git a/opendc-simulator/opendc-simulator-compute/src/main/kotlin/org/opendc/simulator/compute/kernel/cpufreq/ScalingGovernor.kt b/opendc-simulator/opendc-simulator-compute/src/main/java/org/opendc/simulator/compute/kernel/cpufreq/ScalingGovernor.java
index d33827db..69a371e1 100644
--- a/opendc-simulator/opendc-simulator-compute/src/main/kotlin/org/opendc/simulator/compute/kernel/cpufreq/ScalingGovernor.kt
+++ b/opendc-simulator/opendc-simulator-compute/src/main/java/org/opendc/simulator/compute/kernel/cpufreq/ScalingGovernor.java
@@ -1,5 +1,5 @@
/*
- * Copyright (c) 2021 AtLarge Research
+ * Copyright (c) 2022 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,37 +20,27 @@
* SOFTWARE.
*/
-package org.opendc.simulator.compute.kernel.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
* independent of the particular implementation of the CPU.
*
+ * <p>
* Each of the scaling governors implements a single, possibly parametrized, performance scaling algorithm.
*
- * For more information, see the documentation of the Linux CPUFreq subsystem:
- * https://www.kernel.org/doc/html/latest/admin-guide/pm/cpufreq.html
+ * @see <a href="https://www.kernel.org/doc/html/latest/admin-guide/pm/cpufreq.html">documentation of the Linux CPUFreq subsystem</a>.
*/
public interface ScalingGovernor {
/**
- * Create the scaling logic for the specified [policy]
+ * This method is invoked when the governor is started.
*/
- public fun createLogic(policy: ScalingPolicy): Logic
+ default void onStart() {}
/**
- * The logic of the scaling governor.
+ * This method is invoked when the governor should re-decide the frequency limits.
+ *
+ * @param load The load of the system.
*/
- public interface Logic {
- /**
- * This method is invoked when the governor is started.
- */
- public fun onStart() {}
-
- /**
- * This method is invoked when the governor should re-decide the frequency limits.
- *
- * @param load The load of the system.
- */
- public fun onLimit(load: Double) {}
- }
+ default void onLimit(double load) {}
}
diff --git a/opendc-simulator/opendc-simulator-compute/src/main/java/org/opendc/simulator/compute/kernel/cpufreq/ScalingGovernorFactory.java b/opendc-simulator/opendc-simulator-compute/src/main/java/org/opendc/simulator/compute/kernel/cpufreq/ScalingGovernorFactory.java
new file mode 100644
index 00000000..97a49879
--- /dev/null
+++ b/opendc-simulator/opendc-simulator-compute/src/main/java/org/opendc/simulator/compute/kernel/cpufreq/ScalingGovernorFactory.java
@@ -0,0 +1,33 @@
+/*
+ * Copyright (c) 2022 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;
+
+/**
+ * Factory interface for a {@link ScalingGovernor}.
+ */
+public interface ScalingGovernorFactory {
+ /**
+ * Create the scaling logic for the specified {@link ScalingPolicy}.
+ */
+ ScalingGovernor newGovernor(ScalingPolicy policy);
+}
diff --git a/opendc-simulator/opendc-simulator-compute/src/main/java/org/opendc/simulator/compute/kernel/cpufreq/ScalingGovernors.java b/opendc-simulator/opendc-simulator-compute/src/main/java/org/opendc/simulator/compute/kernel/cpufreq/ScalingGovernors.java
new file mode 100644
index 00000000..2b10ae59
--- /dev/null
+++ b/opendc-simulator/opendc-simulator-compute/src/main/java/org/opendc/simulator/compute/kernel/cpufreq/ScalingGovernors.java
@@ -0,0 +1,190 @@
+/*
+ * Copyright (c) 2022 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;
+
+/**
+ * Collection of common {@link ScalingGovernor} implementations.
+ */
+public class ScalingGovernors {
+ private ScalingGovernors() {}
+
+ /**
+ * Return a {@link ScalingGovernorFactory} for the <code>performance</code> scaling governor.
+ *
+ * <p>
+ * This governor causes the highest possible frequency to be requested from the CPUs.
+ */
+ public static ScalingGovernorFactory performance() {
+ return PerformanceScalingGovernor.FACTORY;
+ }
+
+ /**
+ * Return a {@link ScalingGovernorFactory} for the <code>powersave</code> scaling governor.
+ *
+ * <p>
+ * This governor causes the lowest possible frequency to be requested from the CPUs.
+ */
+ public static ScalingGovernorFactory powerSave() {
+ return PowerSaveScalingGovernor.FACTORY;
+ }
+
+ /**
+ * Return a {@link ScalingGovernorFactory} for the <code>conservative</code> scaling governor from the Linux kernel.
+ *
+ * @param threshold The threshold before scaling.
+ * @param stepSize The size of the frequency steps (use negative value for automatic).
+ */
+ public static ScalingGovernorFactory conservative(double threshold, double stepSize) {
+ return (policy) -> new ConservativeScalingGovernor(policy, threshold, stepSize);
+ }
+
+ /**
+ * Return a {@link ScalingGovernorFactory} for the <code>conservative</code> scaling governor from the Linux kernel.
+ *
+ * @param threshold The threshold before scaling.
+ */
+ public static ScalingGovernorFactory conservative(double threshold) {
+ return conservative(threshold, -1.0);
+ }
+
+ /**
+ * Return a {@link ScalingGovernorFactory} for the <code>ondemand</code> scaling governor from the Linux kernel.
+ *
+ * @param threshold The threshold before scaling.
+ */
+ public static ScalingGovernorFactory ondemand(double threshold) {
+ return (policy) -> new OnDemandScalingGovernor(policy, threshold);
+ }
+
+ private abstract static class AbstractScalingGovernor implements ScalingGovernor {
+ protected final ScalingPolicy policy;
+
+ AbstractScalingGovernor(ScalingPolicy policy) {
+ this.policy = policy;
+ }
+ }
+
+ private static class PerformanceScalingGovernor extends AbstractScalingGovernor {
+ static final ScalingGovernorFactory FACTORY = PerformanceScalingGovernor::new;
+
+ private PerformanceScalingGovernor(ScalingPolicy policy) {
+ super(policy);
+ }
+
+ @Override
+ public void onStart() {
+ policy.setTarget(policy.getMax());
+ }
+ }
+
+ private static class PowerSaveScalingGovernor extends AbstractScalingGovernor {
+ static final ScalingGovernorFactory FACTORY = PowerSaveScalingGovernor::new;
+
+ private PowerSaveScalingGovernor(ScalingPolicy policy) {
+ super(policy);
+ }
+
+ @Override
+ public void onStart() {
+ policy.setTarget(policy.getMin());
+ }
+ }
+
+ private static class ConservativeScalingGovernor extends AbstractScalingGovernor {
+ private final double threshold;
+ private final double stepSize;
+ private double previousLoad;
+
+ private ConservativeScalingGovernor(ScalingPolicy policy, double threshold, double stepSize) {
+ super(policy);
+
+ this.threshold = threshold;
+ this.previousLoad = threshold;
+
+ if (stepSize < 0) {
+ // https://github.com/torvalds/linux/blob/master/drivers/cpufreq/cpufreq_conservative.c#L33
+ this.stepSize = policy.getMax() * 0.05;
+ } else {
+ this.stepSize = Math.min(stepSize, policy.getMax());
+ }
+ }
+
+ @Override
+ public void onStart() {
+ policy.setTarget(policy.getMin());
+ }
+
+ @Override
+ public void onLimit(double load) {
+ final ScalingPolicy policy = this.policy;
+ double currentTarget = policy.getTarget();
+ if (load > threshold) {
+ // Check for load increase (see:
+ // https://github.com/torvalds/linux/blob/master/drivers/cpufreq/cpufreq_conservative.c#L102)
+ double step = 0.0;
+
+ if (load > previousLoad) {
+ step = stepSize;
+ } else if (load < previousLoad) {
+ step = -stepSize;
+ }
+
+ double target = Math.min(Math.max(currentTarget + step, policy.getMin()), policy.getMax());
+ policy.setTarget(target);
+ }
+ previousLoad = load;
+ }
+ }
+
+ private static class OnDemandScalingGovernor extends AbstractScalingGovernor {
+ private final double threshold;
+ private final double multiplier;
+
+ private OnDemandScalingGovernor(ScalingPolicy policy, double threshold) {
+ super(policy);
+
+ this.threshold = threshold;
+ this.multiplier = (policy.getMax() - policy.getMin()) / 100;
+ }
+
+ @Override
+ public void onStart() {
+ policy.setTarget(policy.getMin());
+ }
+
+ @Override
+ public void onLimit(double load) {
+ final ScalingPolicy policy = this.policy;
+ double target;
+
+ if (load < threshold) {
+ /* Proportional scaling (see: https://github.com/torvalds/linux/blob/master/drivers/cpufreq/cpufreq_ondemand.c#L151). */
+ target = policy.getMin() + load * multiplier;
+ } else {
+ target = policy.getMax();
+ }
+
+ policy.setTarget(target);
+ }
+ }
+}
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/java/org/opendc/simulator/compute/kernel/cpufreq/ScalingPolicy.java
index f9351896..0cdb7a0b 100644
--- 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/java/org/opendc/simulator/compute/kernel/cpufreq/ScalingPolicy.java
@@ -1,5 +1,5 @@
/*
- * Copyright (c) 2021 AtLarge Research
+ * Copyright (c) 2022 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,32 +20,37 @@
* SOFTWARE.
*/
-package org.opendc.simulator.compute.kernel.cpufreq
+package org.opendc.simulator.compute.kernel.cpufreq;
-import org.opendc.simulator.compute.SimProcessingUnit
+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.
+ * An interface that holds the state managed by a {@link 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
+ SimProcessingUnit getCpu();
/**
- * The target frequency which the CPU should attempt to attain.
+ * Return the target frequency which the CPU should attempt to attain.
*/
- public var target: Double
+ double getTarget();
/**
- * The minimum frequency to which the CPU may scale.
+ * Set the target frequency which the CPU should attempt to attain.
*/
- public val min: Double
+ void setTarget(double target);
/**
- * The maximum frequency to which the CPU may scale.
+ * Return the minimum frequency to which the CPU may scale.
*/
- public val max: Double
+ double getMin();
+
+ /**
+ * Return the maximum frequency to which the CPU may scale.
+ */
+ double getMax();
}
diff --git a/opendc-simulator/opendc-simulator-compute/src/main/java/org/opendc/simulator/compute/kernel/interference/VmInterferenceDomain.java b/opendc-simulator/opendc-simulator-compute/src/main/java/org/opendc/simulator/compute/kernel/interference/VmInterferenceDomain.java
new file mode 100644
index 00000000..cc671379
--- /dev/null
+++ b/opendc-simulator/opendc-simulator-compute/src/main/java/org/opendc/simulator/compute/kernel/interference/VmInterferenceDomain.java
@@ -0,0 +1,136 @@
+/*
+ * Copyright (c) 2022 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 java.util.ArrayDeque;
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.WeakHashMap;
+
+/**
+ * A domain where virtual machines may incur performance variability due to operating on the same resource and
+ * therefore causing interference.
+ */
+public final class VmInterferenceDomain {
+ /**
+ * A cache to maintain a mapping between the active profiles in this domain.
+ */
+ private final WeakHashMap<VmInterferenceProfile, VmInterferenceMember> cache = new WeakHashMap<>();
+
+ /**
+ * The set of members active in this domain.
+ */
+ private final ArrayList<VmInterferenceMember> activeKeys = new ArrayList<>();
+
+ /**
+ * Queue of participants that will be removed or added to the active groups.
+ */
+ private final ArrayDeque<VmInterferenceMember> participants = new ArrayDeque<>();
+
+ /**
+ * Join this interference domain with the specified <code>profile</code> and return the {@link VmInterferenceMember}
+ * associated with the profile. If the member does not exist, it will be created.
+ */
+ public VmInterferenceMember join(VmInterferenceProfile profile) {
+ return cache.computeIfAbsent(profile, (key) -> key.newMember(this));
+ }
+
+ /**
+ * Mark the specified <code>member</code> as active in this interference domain.
+ */
+ void activate(VmInterferenceMember member) {
+ final ArrayList<VmInterferenceMember> activeKeys = this.activeKeys;
+ int pos = Collections.binarySearch(activeKeys, member);
+ if (pos < 0) {
+ activeKeys.add(-pos - 1, member);
+ }
+
+ computeActiveGroups(activeKeys, member);
+ }
+
+ /**
+ * Mark the specified <code>member</code> as inactive in this interference domain.
+ */
+ void deactivate(VmInterferenceMember member) {
+ final ArrayList<VmInterferenceMember> activeKeys = this.activeKeys;
+ activeKeys.remove(member);
+ computeActiveGroups(activeKeys, member);
+ }
+
+ /**
+ * (Re-)compute the active groups.
+ */
+ private void computeActiveGroups(ArrayList<VmInterferenceMember> activeKeys, VmInterferenceMember member) {
+ if (activeKeys.isEmpty()) {
+ return;
+ }
+
+ final int[] groups = member.membership;
+ final int[][] members = member.members;
+ final ArrayDeque<VmInterferenceMember> participants = this.participants;
+
+ for (int group : groups) {
+ int[] groupMembers = members[group];
+
+ int i = 0;
+ int j = 0;
+ int intersection = 0;
+
+ // Compute the intersection of the group members and the current active members
+ while (i < groupMembers.length && j < activeKeys.size()) {
+ int l = groupMembers[i];
+ final VmInterferenceMember rightEntry = activeKeys.get(j);
+ int r = rightEntry.id;
+
+ if (l < r) {
+ i++;
+ } else if (l > r) {
+ j++;
+ } else {
+ if (++intersection > 1) {
+ rightEntry.addGroup(group);
+ } else {
+ participants.add(rightEntry);
+ }
+
+ i++;
+ j++;
+ }
+ }
+
+ while (true) {
+ VmInterferenceMember participant = participants.poll();
+
+ if (participant == null) {
+ break;
+ }
+
+ if (intersection <= 1) {
+ participant.removeGroup(group);
+ } else {
+ participant.addGroup(group);
+ }
+ }
+ }
+ }
+}
diff --git a/opendc-simulator/opendc-simulator-compute/src/main/java/org/opendc/simulator/compute/kernel/interference/VmInterferenceMember.java b/opendc-simulator/opendc-simulator-compute/src/main/java/org/opendc/simulator/compute/kernel/interference/VmInterferenceMember.java
new file mode 100644
index 00000000..64cd5077
--- /dev/null
+++ b/opendc-simulator/opendc-simulator-compute/src/main/java/org/opendc/simulator/compute/kernel/interference/VmInterferenceMember.java
@@ -0,0 +1,177 @@
+/*
+ * Copyright (c) 2022 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 java.util.Arrays;
+import java.util.SplittableRandom;
+import org.jetbrains.annotations.NotNull;
+
+/**
+ * A participant of an interference domain.
+ */
+public final class VmInterferenceMember implements Comparable<VmInterferenceMember> {
+ private final VmInterferenceDomain domain;
+ private final VmInterferenceModel model;
+ final int id;
+ final int[] membership;
+ final int[][] members;
+ private final double[] targets;
+ private final double[] scores;
+
+ private int[] groups = new int[2];
+ private int groupsSize = 0;
+
+ private int refCount = 0;
+
+ VmInterferenceMember(
+ VmInterferenceDomain domain,
+ VmInterferenceModel model,
+ int id,
+ int[] membership,
+ int[][] members,
+ double[] targets,
+ double[] scores) {
+ this.domain = domain;
+ this.model = model;
+ this.id = id;
+ this.membership = membership;
+ this.members = members;
+ this.targets = targets;
+ this.scores = scores;
+ }
+
+ /**
+ * Mark this member as active in this interference domain.
+ */
+ public void activate() {
+ if (refCount++ <= 0) {
+ domain.activate(this);
+ }
+ }
+
+ /**
+ * Mark this member as inactive in this interference domain.
+ */
+ public void deactivate() {
+ if (--refCount <= 0) {
+ domain.deactivate(this);
+ }
+ }
+
+ /**
+ * Compute the performance score of the member in this interference domain.
+ *
+ * @param random The source of randomness to apply when computing the performance score.
+ * @param load The overall load on the interference domain.
+ * @return A score representing the performance score to be applied to the member, with 1
+ * meaning no influence, <1 means that performance degrades, and >1 means that performance improves.
+ */
+ public double apply(SplittableRandom random, double load) {
+ int groupsSize = this.groupsSize;
+
+ if (groupsSize == 0) {
+ return 1.0;
+ }
+
+ int[] groups = this.groups;
+ double[] targets = this.targets;
+
+ int low = 0;
+ int high = groupsSize - 1;
+ int group = -1;
+
+ // Perform binary search over the groups based on target load
+ while (low <= high) {
+ int mid = low + high >>> 1;
+ int midGroup = groups[mid];
+ double target = targets[midGroup];
+
+ if (target < load) {
+ low = mid + 1;
+ group = midGroup;
+ } else if (target > load) {
+ high = mid - 1;
+ } else {
+ group = midGroup;
+ break;
+ }
+ }
+
+ if (group >= 0 && random.nextInt(members[group].length) == 0) {
+ return scores[group];
+ }
+
+ return 1.0;
+ }
+
+ /**
+ * Add an active group to this member.
+ */
+ void addGroup(int group) {
+ int[] groups = this.groups;
+ int groupsSize = this.groupsSize;
+ int pos = Arrays.binarySearch(groups, 0, groupsSize, group);
+
+ if (pos >= 0) {
+ return;
+ }
+
+ int idx = -pos - 1;
+
+ if (groups.length == groupsSize) {
+ int newSize = groupsSize + (groupsSize >> 1);
+ groups = Arrays.copyOf(groups, newSize);
+ this.groups = groups;
+ }
+
+ System.arraycopy(groups, idx, groups, idx + 1, groupsSize - idx);
+ groups[idx] = group;
+ this.groupsSize += 1;
+ }
+
+ /**
+ * Remove an active group from this member.
+ */
+ void removeGroup(int group) {
+ int[] groups = this.groups;
+ int groupsSize = this.groupsSize;
+ int pos = Arrays.binarySearch(groups, 0, groupsSize, group);
+
+ if (pos < 0) {
+ return;
+ }
+
+ System.arraycopy(groups, pos + 1, groups, pos, groupsSize - pos - 1);
+ this.groupsSize -= 1;
+ }
+
+ @Override
+ public int compareTo(@NotNull VmInterferenceMember member) {
+ int cmp = Integer.compare(model.hashCode(), member.model.hashCode());
+ if (cmp != 0) {
+ return cmp;
+ }
+
+ return Integer.compare(id, member.id);
+ }
+}
diff --git a/opendc-simulator/opendc-simulator-compute/src/main/java/org/opendc/simulator/compute/kernel/interference/VmInterferenceModel.java b/opendc-simulator/opendc-simulator-compute/src/main/java/org/opendc/simulator/compute/kernel/interference/VmInterferenceModel.java
new file mode 100644
index 00000000..e2093266
--- /dev/null
+++ b/opendc-simulator/opendc-simulator-compute/src/main/java/org/opendc/simulator/compute/kernel/interference/VmInterferenceModel.java
@@ -0,0 +1,185 @@
+/*
+ * Copyright (c) 2022 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 java.util.ArrayList;
+import java.util.Arrays;
+import java.util.Comparator;
+import java.util.HashMap;
+import java.util.Map;
+import java.util.Set;
+import java.util.TreeMap;
+import java.util.TreeSet;
+import org.jetbrains.annotations.Nullable;
+
+/**
+ * An interference model that models the resource interference between virtual machines on a host.
+ */
+public final class VmInterferenceModel {
+ private final Map<String, Integer> idMapping;
+ private final int[][] members;
+ private final int[][] membership;
+ private final double[] targets;
+ private final double[] scores;
+
+ private VmInterferenceModel(
+ Map<String, Integer> idMapping, int[][] members, int[][] membership, double[] targets, double[] scores) {
+ this.idMapping = idMapping;
+ this.members = members;
+ this.membership = membership;
+ this.targets = targets;
+ this.scores = scores;
+ }
+
+ /**
+ * Create a {@link Builder} for constructing a {@link VmInterferenceModel}.
+ */
+ public static Builder builder() {
+ return new Builder(256);
+ }
+
+ /**
+ * Return the {@link VmInterferenceProfile} associated with the specified <code>id</code>.
+ *
+ * @param id The identifier of the virtual machine.
+ * @return A {@link VmInterferenceProfile} representing the virtual machine as part of interference model or
+ * <code>null</code> if there is no profile for the virtual machine.
+ */
+ @Nullable
+ public VmInterferenceProfile getProfile(String id) {
+ Integer intId = idMapping.get(id);
+ if (intId == null) {
+ return null;
+ }
+ return new VmInterferenceProfile(this, intId, membership[intId], members, targets, scores);
+ }
+
+ /**
+ * Builder class for a {@link VmInterferenceModel}.
+ */
+ public static final class Builder {
+ private double[] targets;
+ private double[] scores;
+ private final ArrayList<Set<String>> members;
+ private final TreeSet<String> ids;
+ private int size;
+
+ private Builder(int initialCapacity) {
+ this.targets = new double[initialCapacity];
+ this.scores = new double[initialCapacity];
+ this.members = new ArrayList<>(initialCapacity);
+ this.ids = new TreeSet<>();
+ }
+
+ /**
+ * Add the specified group to the model.
+ */
+ public Builder addGroup(Set<String> members, double targetLoad, double score) {
+ int size = this.size;
+
+ if (size == targets.length) {
+ grow();
+ }
+
+ targets[size] = targetLoad;
+ scores[size] = score;
+ this.members.add(members);
+ ids.addAll(members);
+
+ this.size++;
+
+ return this;
+ }
+
+ /**
+ * Build the {@link VmInterferenceModel}.
+ */
+ public VmInterferenceModel build() {
+ int size = this.size;
+ double[] targets = this.targets;
+ double[] scores = this.scores;
+ ArrayList<Set<String>> members = this.members;
+
+ Integer[] indices = new Integer[size];
+ Arrays.setAll(indices, (i) -> i);
+ Arrays.sort(
+ indices,
+ Comparator.comparingDouble((Integer l) -> targets[l])
+ .thenComparingDouble(l -> scores[l])
+ .thenComparingInt(l -> l));
+
+ double[] newTargets = new double[size];
+ double[] newScores = new double[size];
+ int[][] newMembers = new int[size][];
+
+ int nextId = 0;
+
+ Map<String, Integer> idMapping = new HashMap<>();
+ TreeMap<String, ArrayList<Integer>> membership = new TreeMap<>();
+ for (String id : ids) {
+ idMapping.put(id, nextId++);
+ membership.put(id, new ArrayList<>());
+ }
+
+ for (int group = 0; group < indices.length; group++) {
+ int j = indices[group];
+ newTargets[group] = targets[j];
+ newScores[group] = scores[j];
+
+ Set<String> groupMembers = members.get(j);
+ int[] newGroupMembers = new int[groupMembers.size()];
+ int k = 0;
+
+ for (String groupMember : groupMembers) {
+ newGroupMembers[k++] = idMapping.get(groupMember);
+ }
+
+ Arrays.sort(newGroupMembers);
+ newMembers[group] = newGroupMembers;
+
+ for (String member : groupMembers) {
+ membership.get(member).add(group);
+ }
+ }
+
+ int[][] newMembership = new int[membership.size()][];
+ int k = 0;
+ for (ArrayList<Integer> value : membership.values()) {
+ newMembership[k++] = value.stream().mapToInt(i -> i).toArray();
+ }
+
+ return new VmInterferenceModel(idMapping, newMembers, newMembership, newTargets, newScores);
+ }
+
+ /**
+ * Helper function to grow the capacity of the internal arrays.
+ */
+ private void grow() {
+ int oldSize = targets.length;
+ int newSize = oldSize + (oldSize >> 1);
+
+ targets = Arrays.copyOf(targets, newSize);
+ scores = Arrays.copyOf(scores, newSize);
+ }
+ }
+}
diff --git a/opendc-simulator/opendc-simulator-compute/src/main/kotlin/org/opendc/simulator/compute/kernel/interference/VmInterferenceProfile.kt b/opendc-simulator/opendc-simulator-compute/src/main/java/org/opendc/simulator/compute/kernel/interference/VmInterferenceProfile.java
index 004dbd07..3f0c0a88 100644
--- a/opendc-simulator/opendc-simulator-compute/src/main/kotlin/org/opendc/simulator/compute/kernel/interference/VmInterferenceProfile.kt
+++ b/opendc-simulator/opendc-simulator-compute/src/main/java/org/opendc/simulator/compute/kernel/interference/VmInterferenceProfile.java
@@ -20,32 +20,41 @@
* SOFTWARE.
*/
-package org.opendc.simulator.compute.kernel.interference
+package org.opendc.simulator.compute.kernel.interference;
/**
* A profile of a particular virtual machine describing its interference pattern with other virtual machines.
- *
- * @param model The model to which this profile belongs.
- * @property id The identifier of the profile inside the model.
- * @property membership The membership of the profile in the groups.
- * @param members The members in the model.
- * @param targets The targets in the model.
- * @param scores The scores in the model.
*/
-public class VmInterferenceProfile internal constructor(
- private val model: VmInterferenceModel,
- private val id: Int,
- private val membership: IntArray,
- private val members: Array<IntArray>,
- private val targets: DoubleArray,
- private val scores: DoubleArray
-) {
+public final class VmInterferenceProfile {
+ private final VmInterferenceModel model;
+ private final int id;
+ private final int[] membership;
+ private final int[][] members;
+ private final double[] targets;
+ private final double[] scores;
+
/**
- * Create a new [VmInterferenceMember] based on this profile for the specified [domain].
+ * Construct a {@link VmInterferenceProfile}.
*/
- internal fun newMember(domain: VmInterferenceDomain): VmInterferenceMember {
- return VmInterferenceMember(domain, model, id, membership, members, targets, scores)
+ VmInterferenceProfile(
+ VmInterferenceModel model, int id, int[] membership, int[][] members, double[] targets, double[] scores) {
+ this.model = model;
+ this.id = id;
+ this.membership = membership;
+ this.members = members;
+ this.targets = targets;
+ this.scores = scores;
}
- override fun toString(): String = "VmInterferenceProfile[id=$id]"
+ /**
+ * Create a new {@link VmInterferenceMember} based on this profile for the specified <code>domain</code>.
+ */
+ VmInterferenceMember newMember(VmInterferenceDomain domain) {
+ return new VmInterferenceMember(domain, model, id, membership, members, targets, scores);
+ }
+
+ @Override
+ public String toString() {
+ return "VmInterferenceProfile[id=" + id + "]";
+ }
}
diff --git a/opendc-simulator/opendc-simulator-compute/src/main/java/org/opendc/simulator/compute/model/MachineModel.java b/opendc-simulator/opendc-simulator-compute/src/main/java/org/opendc/simulator/compute/model/MachineModel.java
new file mode 100644
index 00000000..2c625fce
--- /dev/null
+++ b/opendc-simulator/opendc-simulator-compute/src/main/java/org/opendc/simulator/compute/model/MachineModel.java
@@ -0,0 +1,149 @@
+/*
+ * Copyright (c) 2022 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;
+
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.List;
+import java.util.Objects;
+
+/**
+ * A description of the physical or virtual machine on which a bootable image runs.
+ */
+public final class MachineModel {
+ private final List<ProcessingUnit> cpus;
+ private final List<MemoryUnit> memory;
+ private final List<NetworkAdapter> net;
+ private final List<StorageDevice> storage;
+
+ /**
+ * Construct a {@link MachineModel} instance.
+ *
+ * @param cpus The list of processing units available to the image.
+ * @param memory The list of memory units available to the image.
+ * @param net A list of network adapters available to the machine.
+ * @param storage A list of storage devices available to the machine.
+ */
+ public MachineModel(
+ Iterable<ProcessingUnit> cpus,
+ Iterable<MemoryUnit> memory,
+ Iterable<NetworkAdapter> net,
+ Iterable<StorageDevice> storage) {
+ this.cpus = new ArrayList<>();
+ cpus.forEach(this.cpus::add);
+
+ this.memory = new ArrayList<>();
+ memory.forEach(this.memory::add);
+
+ this.net = new ArrayList<>();
+ net.forEach(this.net::add);
+
+ this.storage = new ArrayList<>();
+ storage.forEach(this.storage::add);
+ }
+
+ /**
+ * Construct a {@link MachineModel} instance.
+ *
+ * @param cpus The list of processing units available to the image.
+ * @param memory The list of memory units available to the image.
+ */
+ public MachineModel(Iterable<ProcessingUnit> cpus, Iterable<MemoryUnit> memory) {
+ this(cpus, memory, Collections.emptyList(), Collections.emptyList());
+ }
+
+ /**
+ * Optimize the [MachineModel] by merging all resources of the same type into a single resource with the combined
+ * capacity. Such configurations can be simulated more efficiently by OpenDC.
+ */
+ public MachineModel optimize() {
+ ProcessingUnit originalCpu = cpus.get(0);
+
+ double freq = 0.0;
+ for (ProcessingUnit cpu : cpus) {
+ freq += cpu.getFrequency();
+ }
+
+ ProcessingNode originalNode = originalCpu.getNode();
+ ProcessingNode processingNode = new ProcessingNode(
+ originalNode.getVendor(), originalNode.getModelName(), originalNode.getArchitecture(), 1);
+ ProcessingUnit processingUnit = new ProcessingUnit(processingNode, originalCpu.getId(), freq);
+
+ long memorySize = 0;
+ for (MemoryUnit mem : memory) {
+ memorySize += mem.getSize();
+ }
+ MemoryUnit memoryUnit = new MemoryUnit("Generic", "Generic", 3200.0, memorySize);
+
+ return new MachineModel(List.of(processingUnit), List.of(memoryUnit));
+ }
+
+ /**
+ * Return the processing units of this machine.
+ */
+ public List<ProcessingUnit> getCpus() {
+ return Collections.unmodifiableList(cpus);
+ }
+
+ /**
+ * Return the memory units of this machine.
+ */
+ public List<MemoryUnit> getMemory() {
+ return Collections.unmodifiableList(memory);
+ }
+
+ /**
+ * Return the network adapters of this machine.
+ */
+ public List<NetworkAdapter> getNetwork() {
+ return Collections.unmodifiableList(net);
+ }
+
+ /**
+ * Return the storage devices of this machine.
+ */
+ public List<StorageDevice> getStorage() {
+ return Collections.unmodifiableList(storage);
+ }
+
+ @Override
+ public boolean equals(Object o) {
+ if (this == o) return true;
+ if (o == null || getClass() != o.getClass()) return false;
+ MachineModel that = (MachineModel) o;
+ return cpus.equals(that.cpus)
+ && memory.equals(that.memory)
+ && net.equals(that.net)
+ && storage.equals(that.storage);
+ }
+
+ @Override
+ public int hashCode() {
+ return Objects.hash(cpus, memory, net, storage);
+ }
+
+ @Override
+ public String toString() {
+ return "MachineModel[cpus=" + cpus + ",memory=" + memory + ",net=" + net + ",storage=" + storage + "]";
+ }
+}
diff --git a/opendc-simulator/opendc-simulator-compute/src/main/java/org/opendc/simulator/compute/model/MemoryUnit.java b/opendc-simulator/opendc-simulator-compute/src/main/java/org/opendc/simulator/compute/model/MemoryUnit.java
new file mode 100644
index 00000000..4250f5a2
--- /dev/null
+++ b/opendc-simulator/opendc-simulator-compute/src/main/java/org/opendc/simulator/compute/model/MemoryUnit.java
@@ -0,0 +1,100 @@
+/*
+ * Copyright (c) 2022 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;
+
+import java.util.Objects;
+
+/**
+ * A memory unit of a compute resource, either virtual or physical.
+ */
+public final class MemoryUnit {
+ private final String vendor;
+ private final String modelName;
+ private final double speed;
+ private final long size;
+
+ /**
+ * Construct a {@link ProcessingNode} instance.
+ *
+ * @param vendor The vendor of the storage device.
+ * @param modelName The model name of the device.
+ * @param speed The access speed of the memory in MHz.
+ * @param size The size of the memory unit in MBs.
+ */
+ public MemoryUnit(String vendor, String modelName, double speed, long size) {
+ this.vendor = vendor;
+ this.modelName = modelName;
+ this.speed = speed;
+ this.size = size;
+ }
+
+ /**
+ * Return the vendor of the storage device.
+ */
+ public String getVendor() {
+ return vendor;
+ }
+
+ /**
+ * Return the model name of the device.
+ */
+ public String getModelName() {
+ return modelName;
+ }
+
+ /**
+ * Return the access speed of the memory in MHz.
+ */
+ public double getSpeed() {
+ return speed;
+ }
+
+ /**
+ * Return the size of the memory unit in MBs.
+ */
+ public long getSize() {
+ return size;
+ }
+
+ @Override
+ public boolean equals(Object o) {
+ if (this == o) return true;
+ if (o == null || getClass() != o.getClass()) return false;
+ MemoryUnit that = (MemoryUnit) o;
+ return Double.compare(that.speed, speed) == 0
+ && size == that.size
+ && vendor.equals(that.vendor)
+ && modelName.equals(that.modelName);
+ }
+
+ @Override
+ public int hashCode() {
+ return Objects.hash(vendor, modelName, speed, size);
+ }
+
+ @Override
+ public String toString() {
+ return "ProcessingNode[vendor='" + vendor + "',modelName='" + modelName + "',speed=" + speed + "MHz,size="
+ + size + "MB]";
+ }
+}
diff --git a/opendc-simulator/opendc-simulator-compute/src/main/java/org/opendc/simulator/compute/model/NetworkAdapter.java b/opendc-simulator/opendc-simulator-compute/src/main/java/org/opendc/simulator/compute/model/NetworkAdapter.java
new file mode 100644
index 00000000..ff3daa40
--- /dev/null
+++ b/opendc-simulator/opendc-simulator-compute/src/main/java/org/opendc/simulator/compute/model/NetworkAdapter.java
@@ -0,0 +1,88 @@
+/*
+ * Copyright (c) 2022 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;
+
+import java.util.Objects;
+
+/**
+ * A description of a network adapter
+ */
+public final class NetworkAdapter {
+ private final String vendor;
+ private final String modelName;
+ private final double bandwidth;
+
+ /**
+ * Construct a {@link NetworkAdapter} instance.
+ *
+ * @param vendor The vendor of the storage device.
+ * @param modelName The model name of the device.
+ * @param bandwidth The bandwidth of the network adapter in Mbps.
+ */
+ public NetworkAdapter(String vendor, String modelName, double bandwidth) {
+ this.vendor = vendor;
+ this.modelName = modelName;
+ this.bandwidth = bandwidth;
+ }
+
+ /**
+ * Return the vendor of the storage device.
+ */
+ public String getVendor() {
+ return vendor;
+ }
+
+ /**
+ * Return the model name of the device.
+ */
+ public String getModelName() {
+ return modelName;
+ }
+
+ /**
+ * Return the bandwidth of the network adapter in Mbps.
+ */
+ public double getBandwidth() {
+ return bandwidth;
+ }
+
+ @Override
+ public boolean equals(Object o) {
+ if (this == o) return true;
+ if (o == null || getClass() != o.getClass()) return false;
+ NetworkAdapter that = (NetworkAdapter) o;
+ return Double.compare(that.bandwidth, bandwidth) == 0
+ && vendor.equals(that.vendor)
+ && modelName.equals(that.modelName);
+ }
+
+ @Override
+ public int hashCode() {
+ return Objects.hash(vendor, modelName, bandwidth);
+ }
+
+ @Override
+ public String toString() {
+ return "NetworkAdapter[vendor='" + vendor + "',modelName='" + modelName + "',bandwidth=" + bandwidth + "Mbps]";
+ }
+}
diff --git a/opendc-simulator/opendc-simulator-compute/src/main/java/org/opendc/simulator/compute/model/ProcessingNode.java b/opendc-simulator/opendc-simulator-compute/src/main/java/org/opendc/simulator/compute/model/ProcessingNode.java
new file mode 100644
index 00000000..01a87b96
--- /dev/null
+++ b/opendc-simulator/opendc-simulator-compute/src/main/java/org/opendc/simulator/compute/model/ProcessingNode.java
@@ -0,0 +1,100 @@
+/*
+ * Copyright (c) 2022 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;
+
+import java.util.Objects;
+
+/**
+ * A processing node/package/socket containing possibly several CPU cores.
+ */
+public final class ProcessingNode {
+ private final String vendor;
+ private final String modelName;
+ private final String arch;
+ private final int coreCount;
+
+ /**
+ * Construct a {@link ProcessingNode} instance.
+ *
+ * @param vendor The vendor of the storage device.
+ * @param modelName The model name of the device.
+ * @param arch The micro-architecture of the processor node.
+ * @param coreCount The number of logical CPUs in the processor node.
+ */
+ public ProcessingNode(String vendor, String modelName, String arch, int coreCount) {
+ this.vendor = vendor;
+ this.modelName = modelName;
+ this.arch = arch;
+ this.coreCount = coreCount;
+ }
+
+ /**
+ * Return the vendor of the storage device.
+ */
+ public String getVendor() {
+ return vendor;
+ }
+
+ /**
+ * Return the model name of the device.
+ */
+ public String getModelName() {
+ return modelName;
+ }
+
+ /**
+ * Return the micro-architecture of the processor node.
+ */
+ public String getArchitecture() {
+ return arch;
+ }
+
+ /**
+ * Return the number of logical CPUs in the processor node.
+ */
+ public int getCoreCount() {
+ return coreCount;
+ }
+
+ @Override
+ public boolean equals(Object o) {
+ if (this == o) return true;
+ if (o == null || getClass() != o.getClass()) return false;
+ ProcessingNode that = (ProcessingNode) o;
+ return coreCount == that.coreCount
+ && vendor.equals(that.vendor)
+ && modelName.equals(that.modelName)
+ && arch.equals(that.arch);
+ }
+
+ @Override
+ public int hashCode() {
+ return Objects.hash(vendor, modelName, arch, coreCount);
+ }
+
+ @Override
+ public String toString() {
+ return "ProcessingNode[vendor='" + vendor + "',modelName='" + modelName + "',arch=" + arch + ",coreCount="
+ + coreCount + "]";
+ }
+}
diff --git a/opendc-simulator/opendc-simulator-compute/src/main/java/org/opendc/simulator/compute/model/ProcessingUnit.java b/opendc-simulator/opendc-simulator-compute/src/main/java/org/opendc/simulator/compute/model/ProcessingUnit.java
new file mode 100644
index 00000000..51a045d1
--- /dev/null
+++ b/opendc-simulator/opendc-simulator-compute/src/main/java/org/opendc/simulator/compute/model/ProcessingUnit.java
@@ -0,0 +1,86 @@
+/*
+ * Copyright (c) 2022 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;
+
+import java.util.Objects;
+
+/**
+ * A single logical compute unit of processor node, either virtual or physical.
+ */
+public final class ProcessingUnit {
+ private final ProcessingNode node;
+ private final int id;
+ private final double frequency;
+
+ /**
+ * Construct a {@link ProcessingUnit} instance.
+ *
+ * @param node The processing node containing the CPU core.
+ * @param id The identifier of the CPU core within the processing node.
+ * @param frequency The clock rate of the CPU in MHz.
+ */
+ public ProcessingUnit(ProcessingNode node, int id, double frequency) {
+ this.node = node;
+ this.id = id;
+ this.frequency = frequency;
+ }
+
+ /**
+ * Return the processing node containing the CPU core.
+ */
+ public ProcessingNode getNode() {
+ return node;
+ }
+
+ /**
+ * Return the identifier of the CPU core within the processing node.
+ */
+ public int getId() {
+ return id;
+ }
+
+ /**
+ * Return the clock rate of the CPU in MHz.
+ */
+ public double getFrequency() {
+ return frequency;
+ }
+
+ @Override
+ public boolean equals(Object o) {
+ if (this == o) return true;
+ if (o == null || getClass() != o.getClass()) return false;
+ ProcessingUnit that = (ProcessingUnit) o;
+ return id == that.id && Double.compare(that.frequency, frequency) == 0 && Objects.equals(node, that.node);
+ }
+
+ @Override
+ public int hashCode() {
+ return Objects.hash(node, id, frequency);
+ }
+
+ @Override
+ public String toString() {
+ return "ProcessingUnit[node=" + node + ",id=" + id + ",frequency=" + frequency + "]";
+ }
+}
diff --git a/opendc-simulator/opendc-simulator-compute/src/main/java/org/opendc/simulator/compute/model/StorageDevice.java b/opendc-simulator/opendc-simulator-compute/src/main/java/org/opendc/simulator/compute/model/StorageDevice.java
new file mode 100644
index 00000000..549ccc7e
--- /dev/null
+++ b/opendc-simulator/opendc-simulator-compute/src/main/java/org/opendc/simulator/compute/model/StorageDevice.java
@@ -0,0 +1,112 @@
+/*
+ * Copyright (c) 2022 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;
+
+import java.util.Objects;
+
+/**
+ * Model for a physical storage device attached to a machine.
+ */
+public final class StorageDevice {
+ private final String vendor;
+ private final String modelName;
+ private final double capacity;
+ private final double readBandwidth;
+ private final double writeBandwidth;
+
+ /**
+ * Construct a {@link StorageDevice} instance.
+ *
+ * @param vendor The vendor of the storage device.
+ * @param modelName The model name of the device.
+ * @param capacity The capacity of the device.
+ * @param readBandwidth The read bandwidth of the device in MBps.
+ * @param writeBandwidth The write bandwidth of the device in MBps.
+ */
+ public StorageDevice(
+ String vendor, String modelName, double capacity, double readBandwidth, double writeBandwidth) {
+ this.vendor = vendor;
+ this.modelName = modelName;
+ this.capacity = capacity;
+ this.readBandwidth = readBandwidth;
+ this.writeBandwidth = writeBandwidth;
+ }
+
+ /**
+ * Return the vendor of the storage device.
+ */
+ public String getVendor() {
+ return vendor;
+ }
+
+ /**
+ * Return the model name of the device.
+ */
+ public String getModelName() {
+ return modelName;
+ }
+
+ /**
+ * Return the capacity of the device.
+ */
+ public double getCapacity() {
+ return capacity;
+ }
+
+ /**
+ * Return the read bandwidth of the device in MBps.
+ */
+ public double getReadBandwidth() {
+ return readBandwidth;
+ }
+
+ /**
+ * Return the write bandwidth of the device in MBps.
+ */
+ public double getWriteBandwidth() {
+ return writeBandwidth;
+ }
+
+ @Override
+ public boolean equals(Object o) {
+ if (this == o) return true;
+ if (o == null || getClass() != o.getClass()) return false;
+ StorageDevice that = (StorageDevice) o;
+ return Double.compare(that.capacity, capacity) == 0
+ && Double.compare(that.readBandwidth, readBandwidth) == 0
+ && Double.compare(that.writeBandwidth, writeBandwidth) == 0
+ && vendor.equals(that.vendor)
+ && modelName.equals(that.modelName);
+ }
+
+ @Override
+ public int hashCode() {
+ return Objects.hash(vendor, modelName, capacity, readBandwidth, writeBandwidth);
+ }
+
+ @Override
+ public String toString() {
+ return "StorageDevice[vendor='" + vendor + "',modelName='" + modelName + "',capacity=" + capacity
+ + ",readBandwidth=" + readBandwidth + ",writeBandwidth=" + writeBandwidth + "]";
+ }
+}
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/java/org/opendc/simulator/compute/power/CpuPowerModel.java
index decb2420..e023d098 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/java/org/opendc/simulator/compute/power/CpuPowerModel.java
@@ -1,5 +1,5 @@
/*
- * Copyright (c) 2021 AtLarge Research
+ * Copyright (c) 2022 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,19 +20,19 @@
* SOFTWARE.
*/
-package org.opendc.simulator.compute.power
+package org.opendc.simulator.compute.power;
-import org.opendc.simulator.compute.SimMachine
+import org.opendc.simulator.compute.SimMachine;
/**
- * A model for estimating the power usage of a [SimMachine].
+ * A model for estimating the power usage of a {@link SimMachine} based on the CPU usage.
*/
-public interface PowerModel {
+public interface CpuPowerModel {
/**
* Computes CPU power consumption for each host.
*
* @param utilization The CPU utilization percentage.
- * @return A [Double] value of CPU power consumption.
+ * @return A double value of CPU power consumption (in W).
*/
- public fun computePower(utilization: Double): Double
+ double computePower(double utilization);
}
diff --git a/opendc-simulator/opendc-simulator-compute/src/main/java/org/opendc/simulator/compute/power/CpuPowerModels.java b/opendc-simulator/opendc-simulator-compute/src/main/java/org/opendc/simulator/compute/power/CpuPowerModels.java
new file mode 100644
index 00000000..5d3d936b
--- /dev/null
+++ b/opendc-simulator/opendc-simulator-compute/src/main/java/org/opendc/simulator/compute/power/CpuPowerModels.java
@@ -0,0 +1,330 @@
+/*
+ * Copyright (c) 2022 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 java.util.Arrays;
+
+/**
+ * A collection {@link CpuPowerModel} implementations.
+ */
+public class CpuPowerModels {
+ private CpuPowerModels() {}
+
+ /**
+ * Construct a constant {@link CpuPowerModel}.
+ *
+ * @param power The power consumption fo the server at all times (in W).
+ */
+ public static CpuPowerModel constant(double power) {
+ return new ConstantPowerModel(power);
+ }
+
+ /**
+ * Construct a square root {@link CpuPowerModel} that is adapted from CloudSim.
+ *
+ * @param maxPower The maximum power draw of the server in W.
+ * @param idlePower The power draw of the server at its lowest utilization level in W.
+ */
+ public static CpuPowerModel sqrt(double maxPower, double idlePower) {
+ return new SqrtPowerModel(maxPower, idlePower);
+ }
+
+ /**
+ * Construct a linear {@link CpuPowerModel} that is adapted from CloudSim.
+ *
+ * @param maxPower The maximum power draw of the server in W.
+ * @param idlePower The power draw of the server at its lowest utilization level in W.
+ */
+ public static CpuPowerModel linear(double maxPower, double idlePower) {
+ return new LinearPowerModel(maxPower, idlePower);
+ }
+
+ /**
+ * Construct a square {@link CpuPowerModel} that is adapted from CloudSim.
+ *
+ * @param maxPower The maximum power draw of the server in W.
+ * @param idlePower The power draw of the server at its lowest utilization level in W.
+ */
+ public static CpuPowerModel square(double maxPower, double idlePower) {
+ return new SquarePowerModel(maxPower, idlePower);
+ }
+
+ /**
+ * Construct a cubic {@link CpuPowerModel} that is adapted from CloudSim.
+ *
+ * @param maxPower The maximum power draw of the server in W.
+ * @param idlePower The power draw of the server at its lowest utilization level in W.
+ */
+ public static CpuPowerModel cubic(double maxPower, double idlePower) {
+ return new CubicPowerModel(maxPower, idlePower);
+ }
+
+ /**
+ * Construct a {@link CpuPowerModel} that minimizes the mean squared error (MSE)
+ * to the actual power measurement by tuning the calibration parameter.
+ *
+ * @param maxPower The maximum power draw of the server in W.
+ * @param idlePower The power draw of the server at its lowest utilization level in W.
+ * @param calibrationFactor The parameter set to minimize the MSE.
+ * @see <a href="https://dl.acm.org/doi/abs/10.1145/1273440.1250665">
+ * Fan et al., Power provisioning for a warehouse-sized computer, ACM SIGARCH'07</a>
+ */
+ public static CpuPowerModel mse(double maxPower, double idlePower, double calibrationFactor) {
+ return new MsePowerModel(maxPower, idlePower, calibrationFactor);
+ }
+
+ /**
+ * Construct an asymptotic {@link CpuPowerModel} adapted from GreenCloud.
+ *
+ * @param maxPower The maximum power draw of the server in W.
+ * @param idlePower The power draw of the server at its lowest utilization level in W.
+ * @param asymUtil A utilization level at which the server attains asymptotic,
+ * i.e., close to linear power consumption versus the offered load.
+ * For most of the CPUs,a is in [0.2, 0.5].
+ * @param dvfs A flag indicates whether DVFS is enabled.
+ */
+ public static CpuPowerModel asymptotic(double maxPower, double idlePower, double asymUtil, boolean dvfs) {
+ return new AsymptoticPowerModel(maxPower, idlePower, asymUtil, dvfs);
+ }
+
+ /**
+ * Construct a linear interpolation model {@link CpuPowerModel} that is adapted from CloudSim.
+ *
+ * <p>
+ * The power consumption is linearly interpolated over the given power levels. In case of two values, the first
+ * represents 0% utilization, while the last value represent 100% utilization.
+ *
+ * @param powerLevels An array of power consumption steps (in W) for a specific CPU utilization.
+ * @see <a href="http://www.spec.org/power_ssj2008/results/res2011q1/">Machines used in the SPEC benchmark</a>
+ */
+ public static CpuPowerModel interpolate(double... powerLevels) {
+ return new InterpolationPowerModel(powerLevels.clone());
+ }
+
+ /**
+ * Decorate an existing {@link CpuPowerModel} to ensure that zero power consumption is reported when there is no
+ * utilization.
+ *
+ * @param delegate The existing {@link CpuPowerModel} to decorate.
+ */
+ public static CpuPowerModel zeroIdle(CpuPowerModel delegate) {
+ return new ZeroIdlePowerDecorator(delegate);
+ }
+
+ private static final class ConstantPowerModel implements CpuPowerModel {
+ private final double power;
+
+ ConstantPowerModel(double power) {
+ this.power = power;
+ }
+
+ @Override
+ public double computePower(double utilization) {
+ return power;
+ }
+
+ @Override
+ public String toString() {
+ return "ConstantPowerModel[power=" + power + "]";
+ }
+ }
+
+ private abstract static class MaxIdlePowerModel implements CpuPowerModel {
+ protected final double maxPower;
+ protected final double idlePower;
+
+ MaxIdlePowerModel(double maxPower, double idlePower) {
+ this.maxPower = maxPower;
+ this.idlePower = idlePower;
+ }
+
+ @Override
+ public String toString() {
+ return getClass().getSimpleName() + "[max=" + maxPower + ",idle=" + idlePower + "]";
+ }
+ }
+
+ private static final class SqrtPowerModel extends MaxIdlePowerModel {
+ private final double factor;
+
+ SqrtPowerModel(double maxPower, double idlePower) {
+ super(maxPower, idlePower);
+ this.factor = (maxPower - idlePower) / Math.sqrt(100);
+ }
+
+ @Override
+ public double computePower(double utilization) {
+ return idlePower + factor * Math.sqrt(utilization * 100);
+ }
+ }
+
+ private static final class LinearPowerModel extends MaxIdlePowerModel {
+ private final double factor;
+
+ LinearPowerModel(double maxPower, double idlePower) {
+ super(maxPower, idlePower);
+ this.factor = (maxPower - idlePower) / 100;
+ }
+
+ @Override
+ public double computePower(double utilization) {
+ return idlePower + factor * utilization * 100;
+ }
+ }
+
+ private static final class SquarePowerModel extends MaxIdlePowerModel {
+ private final double factor;
+
+ SquarePowerModel(double maxPower, double idlePower) {
+ super(maxPower, idlePower);
+ this.factor = (maxPower - idlePower) / Math.pow(100, 2);
+ }
+
+ @Override
+ public double computePower(double utilization) {
+ return idlePower + factor * Math.pow(utilization * 100, 2);
+ }
+ }
+
+ private static final class CubicPowerModel extends MaxIdlePowerModel {
+ private final double factor;
+
+ CubicPowerModel(double maxPower, double idlePower) {
+ super(maxPower, idlePower);
+ this.factor = (maxPower - idlePower) / Math.pow(100, 3);
+ }
+
+ @Override
+ public double computePower(double utilization) {
+ return idlePower + factor * Math.pow(utilization * 100, 3);
+ }
+ }
+
+ private static final class MsePowerModel extends MaxIdlePowerModel {
+ private final double calibrationFactor;
+ private final double factor;
+
+ MsePowerModel(double maxPower, double idlePower, double calibrationFactor) {
+ super(maxPower, idlePower);
+ this.calibrationFactor = calibrationFactor;
+ this.factor = (maxPower - idlePower) / 100;
+ }
+
+ @Override
+ public double computePower(double utilization) {
+ return idlePower + factor * (2 * utilization - Math.pow(utilization, calibrationFactor)) * 100;
+ }
+
+ @Override
+ public String toString() {
+ return "MsePowerModel[max=" + maxPower + ",idle=" + idlePower + ",calibrationFactor=" + calibrationFactor
+ + "]";
+ }
+ }
+
+ private static final class AsymptoticPowerModel extends MaxIdlePowerModel {
+ private final double asymUtil;
+ private final boolean dvfs;
+ private final double factor;
+
+ AsymptoticPowerModel(double maxPower, double idlePower, double asymUtil, boolean dvfs) {
+ super(maxPower, idlePower);
+ this.asymUtil = asymUtil;
+ this.dvfs = dvfs;
+ this.factor = (maxPower - idlePower) / 100;
+ }
+
+ @Override
+ public double computePower(double utilization) {
+ if (dvfs) {
+ return idlePower
+ + (factor * 100)
+ / 2
+ * (1
+ + Math.pow(utilization, 3)
+ - Math.pow(Math.E, -Math.pow(utilization, 3) / asymUtil));
+ } else {
+ return idlePower + (factor * 100) / 2 * (1 + utilization - Math.pow(Math.E, -utilization / asymUtil));
+ }
+ }
+
+ @Override
+ public String toString() {
+ return "AsymptoticPowerModel[max=" + maxPower + ",idle=" + idlePower + ",asymUtil=" + asymUtil + ",dvfs="
+ + dvfs + "]";
+ }
+ }
+
+ private static final class InterpolationPowerModel implements CpuPowerModel {
+ private final double[] powerLevels;
+
+ InterpolationPowerModel(double[] powerLevels) {
+ this.powerLevels = powerLevels;
+ }
+
+ @Override
+ public double computePower(double utilization) {
+ final double[] powerLevels = this.powerLevels;
+ double clampedUtilization = Math.min(1.0, Math.max(0.0, utilization));
+
+ if (utilization % 0.1 == 0.0) {
+ return powerLevels[(int) (clampedUtilization * 10)];
+ }
+
+ int utilizationFlr = (int) Math.floor(clampedUtilization * 10);
+ int utilizationCil = (int) Math.ceil(clampedUtilization * 10);
+ double powerFlr = powerLevels[utilizationFlr];
+ double powerCil = powerLevels[utilizationCil];
+ double delta = (powerCil - powerFlr) / 10;
+
+ return powerFlr + delta * (clampedUtilization - utilizationFlr / 10.0) * 100;
+ }
+
+ @Override
+ public String toString() {
+ return "InterpolationPowerModel[levels=" + Arrays.toString(powerLevels) + "]";
+ }
+ }
+
+ private static final class ZeroIdlePowerDecorator implements CpuPowerModel {
+ private final CpuPowerModel delegate;
+
+ ZeroIdlePowerDecorator(CpuPowerModel delegate) {
+ this.delegate = delegate;
+ }
+
+ @Override
+ public double computePower(double utilization) {
+ if (utilization == 0.0) {
+ return 0.0;
+ }
+
+ return delegate.computePower(utilization);
+ }
+
+ @Override
+ public String toString() {
+ return "ZeroIdlePowerDecorator[delegate=" + delegate + "]";
+ }
+ }
+}
diff --git a/opendc-simulator/opendc-simulator-compute/src/main/java/org/opendc/simulator/compute/workload/SimFlopsWorkload.java b/opendc-simulator/opendc-simulator-compute/src/main/java/org/opendc/simulator/compute/workload/SimFlopsWorkload.java
new file mode 100644
index 00000000..255fd1b2
--- /dev/null
+++ b/opendc-simulator/opendc-simulator-compute/src/main/java/org/opendc/simulator/compute/workload/SimFlopsWorkload.java
@@ -0,0 +1,141 @@
+/*
+ * Copyright (c) 2022 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 java.util.List;
+import org.opendc.simulator.compute.SimMachineContext;
+import org.opendc.simulator.compute.SimProcessingUnit;
+import org.opendc.simulator.flow2.FlowGraph;
+import org.opendc.simulator.flow2.FlowStage;
+import org.opendc.simulator.flow2.FlowStageLogic;
+import org.opendc.simulator.flow2.OutPort;
+
+/**
+ * A {@link SimWorkload} that models applications as a static number of floating point operations executed on
+ * multiple cores of a compute resource.
+ */
+public class SimFlopsWorkload implements SimWorkload, FlowStageLogic {
+ private final long flops;
+ private final double utilization;
+
+ private SimMachineContext ctx;
+ private FlowStage stage;
+ private OutPort[] outputs;
+
+ private float remainingAmount;
+ private long lastUpdate;
+
+ /**
+ * Construct a new {@link SimFlopsWorkload}.
+ *
+ * @param flops The number of floating point operations to perform for this task in MFLOPs.
+ * @param utilization A model of the CPU utilization of the application.
+ */
+ public SimFlopsWorkload(long flops, double utilization) {
+ if (flops < 0) {
+ throw new IllegalArgumentException("Number of FLOPs must be positive");
+ } else if (utilization <= 0.0 || utilization > 1.0) {
+ throw new IllegalArgumentException("Utilization must be in (0, 1]");
+ }
+
+ this.flops = flops;
+ this.utilization = utilization;
+ }
+
+ @Override
+ public void onStart(SimMachineContext ctx) {
+ this.ctx = ctx;
+
+ final FlowGraph graph = ctx.getGraph();
+ final FlowStage stage = graph.newStage(this);
+ this.stage = stage;
+
+ final List<? extends SimProcessingUnit> cpus = ctx.getCpus();
+ final OutPort[] outputs = new OutPort[cpus.size()];
+ this.outputs = outputs;
+
+ for (int i = 0; i < cpus.size(); i++) {
+ final SimProcessingUnit cpu = cpus.get(i);
+ final OutPort output = stage.getOutlet("cpu" + i);
+
+ graph.connect(output, cpu.getInput());
+ outputs[i] = output;
+ }
+
+ this.remainingAmount = flops;
+ this.lastUpdate = graph.getEngine().getClock().millis();
+ }
+
+ @Override
+ public void onStop(SimMachineContext ctx) {
+ this.ctx = null;
+
+ final FlowStage stage = this.stage;
+ if (stage != null) {
+ this.stage = null;
+ stage.close();
+ }
+ }
+
+ @Override
+ public String toString() {
+ return "SimFlopsWorkload[FLOPs=" + flops + ",utilization=" + utilization + "]";
+ }
+
+ @Override
+ public long onUpdate(FlowStage ctx, long now) {
+ long lastUpdate = this.lastUpdate;
+ this.lastUpdate = now;
+
+ long delta = Math.max(0, now - lastUpdate);
+
+ float consumed = 0.f;
+ float limit = 0.f;
+
+ for (final OutPort output : outputs) {
+ consumed += output.getRate() * delta;
+
+ float outputLimit = (float) (output.getCapacity() * utilization);
+ limit += outputLimit;
+
+ output.push(outputLimit);
+ }
+ consumed = (float) (consumed * 0.001);
+
+ float remainingAmount = this.remainingAmount - consumed;
+ this.remainingAmount = remainingAmount;
+
+ long duration = (long) Math.ceil(remainingAmount / limit * 1000);
+
+ if (duration <= 0) {
+ final SimMachineContext machineContext = this.ctx;
+ if (machineContext != null) {
+ machineContext.shutdown();
+ }
+ ctx.close();
+ return Long.MAX_VALUE;
+ }
+
+ return now + duration;
+ }
+}
diff --git a/opendc-simulator/opendc-simulator-compute/src/main/java/org/opendc/simulator/compute/workload/SimRuntimeWorkload.java b/opendc-simulator/opendc-simulator-compute/src/main/java/org/opendc/simulator/compute/workload/SimRuntimeWorkload.java
new file mode 100644
index 00000000..c3380b31
--- /dev/null
+++ b/opendc-simulator/opendc-simulator-compute/src/main/java/org/opendc/simulator/compute/workload/SimRuntimeWorkload.java
@@ -0,0 +1,131 @@
+/*
+ * Copyright (c) 2022 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 java.util.List;
+import org.opendc.simulator.compute.SimMachineContext;
+import org.opendc.simulator.compute.SimProcessingUnit;
+import org.opendc.simulator.flow2.FlowGraph;
+import org.opendc.simulator.flow2.FlowStage;
+import org.opendc.simulator.flow2.FlowStageLogic;
+import org.opendc.simulator.flow2.OutPort;
+
+/**
+ * A [SimWorkload] that models application execution as a single duration.
+ */
+public class SimRuntimeWorkload implements SimWorkload, FlowStageLogic {
+ private final long duration;
+ private final double utilization;
+
+ private SimMachineContext ctx;
+ private FlowStage stage;
+ private OutPort[] outputs;
+
+ private long remainingDuration;
+ private long lastUpdate;
+
+ /**
+ * Construct a new {@link SimRuntimeWorkload}.
+ *
+ * @param duration The duration of the workload in milliseconds.
+ * @param utilization A model of the CPU utilization of the application.
+ */
+ public SimRuntimeWorkload(long duration, double utilization) {
+ if (duration < 0) {
+ throw new IllegalArgumentException("Duration must be positive");
+ } else if (utilization <= 0.0 || utilization > 1.0) {
+ throw new IllegalArgumentException("Utilization must be in (0, 1]");
+ }
+
+ this.duration = duration;
+ this.utilization = utilization;
+ }
+
+ @Override
+ public void onStart(SimMachineContext ctx) {
+ this.ctx = ctx;
+
+ final FlowGraph graph = ctx.getGraph();
+ final FlowStage stage = graph.newStage(this);
+ this.stage = stage;
+
+ final List<? extends SimProcessingUnit> cpus = ctx.getCpus();
+ final OutPort[] outputs = new OutPort[cpus.size()];
+ this.outputs = outputs;
+
+ for (int i = 0; i < cpus.size(); i++) {
+ final SimProcessingUnit cpu = cpus.get(i);
+ final OutPort output = stage.getOutlet("cpu" + i);
+
+ graph.connect(output, cpu.getInput());
+ outputs[i] = output;
+ }
+
+ this.remainingDuration = duration;
+ this.lastUpdate = graph.getEngine().getClock().millis();
+ }
+
+ @Override
+ public void onStop(SimMachineContext ctx) {
+ this.ctx = null;
+
+ final FlowStage stage = this.stage;
+ if (stage != null) {
+ this.stage = null;
+ this.outputs = null;
+ stage.close();
+ }
+ }
+
+ @Override
+ public long onUpdate(FlowStage ctx, long now) {
+ long lastUpdate = this.lastUpdate;
+ this.lastUpdate = now;
+
+ long delta = now - lastUpdate;
+ long duration = this.remainingDuration - delta;
+
+ if (duration <= 0) {
+ final SimMachineContext machineContext = this.ctx;
+ if (machineContext != null) {
+ machineContext.shutdown();
+ }
+ ctx.close();
+ return Long.MAX_VALUE;
+ }
+
+ this.remainingDuration = duration;
+
+ for (final OutPort output : outputs) {
+ float limit = (float) (output.getCapacity() * utilization);
+ output.push(limit);
+ }
+
+ return now + duration;
+ }
+
+ @Override
+ public String toString() {
+ return "SimDurationWorkload[duration=" + duration + "ms,utilization=" + utilization + "]";
+ }
+}
diff --git a/opendc-simulator/opendc-simulator-compute/src/main/java/org/opendc/simulator/compute/workload/SimTrace.java b/opendc-simulator/opendc-simulator-compute/src/main/java/org/opendc/simulator/compute/workload/SimTrace.java
new file mode 100644
index 00000000..12a567ff
--- /dev/null
+++ b/opendc-simulator/opendc-simulator-compute/src/main/java/org/opendc/simulator/compute/workload/SimTrace.java
@@ -0,0 +1,422 @@
+/*
+ * Copyright (c) 2022 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 java.util.Arrays;
+import java.util.List;
+import org.opendc.simulator.compute.SimMachineContext;
+import org.opendc.simulator.compute.SimProcessingUnit;
+import org.opendc.simulator.flow2.FlowGraph;
+import org.opendc.simulator.flow2.FlowStage;
+import org.opendc.simulator.flow2.FlowStageLogic;
+import org.opendc.simulator.flow2.OutPort;
+
+/**
+ * A workload trace that describes the resource utilization over time in a collection of {@link SimTraceFragment}s.
+ */
+public final class SimTrace {
+ private final double[] usageCol;
+ private final long[] deadlineCol;
+ private final int[] coresCol;
+ private final int size;
+
+ /**
+ * Construct a {@link SimTrace} instance.
+ *
+ * @param usageCol The column containing the CPU usage of each fragment (in MHz).
+ * @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.
+ */
+ private SimTrace(double[] usageCol, long[] deadlineCol, int[] coresCol, int size) {
+ if (size < 0) {
+ throw new IllegalArgumentException("Invalid trace size");
+ } else if (usageCol.length < size) {
+ throw new IllegalArgumentException("Invalid number of usage entries");
+ } else if (deadlineCol.length < size) {
+ throw new IllegalArgumentException("Invalid number of deadline entries");
+ } else if (coresCol.length < size) {
+ throw new IllegalArgumentException("Invalid number of core entries");
+ }
+
+ this.usageCol = usageCol;
+ this.deadlineCol = deadlineCol;
+ this.coresCol = coresCol;
+ this.size = size;
+ }
+
+ /**
+ * Construct a {@link SimWorkload} for this trace.
+ *
+ * @param offset The offset for the timestamps.
+ */
+ public SimWorkload createWorkload(long offset) {
+ return new Workload(offset, usageCol, deadlineCol, coresCol, size);
+ }
+
+ /**
+ * Create a new {@link Builder} instance with the specified initial capacity.
+ */
+ public static Builder builder(int initialCapacity) {
+ return new Builder(initialCapacity);
+ }
+
+ /**
+ * Create a new {@link Builder} instance with a default initial capacity.
+ */
+ public static Builder builder() {
+ return builder(256);
+ }
+
+ /**
+ * Construct a {@link SimTrace} from the specified fragments.
+ *
+ * @param fragments The array of fragments to construct the trace from.
+ */
+ public static SimTrace ofFragments(SimTraceFragment... fragments) {
+ final Builder builder = builder(fragments.length);
+
+ for (SimTraceFragment fragment : fragments) {
+ builder.add(fragment.timestamp + fragment.duration, fragment.usage, fragment.cores);
+ }
+
+ return builder.build();
+ }
+
+ /**
+ * Construct a {@link SimTrace} from the specified fragments.
+ *
+ * @param fragments The fragments to construct the trace from.
+ */
+ public static SimTrace ofFragments(List<SimTraceFragment> fragments) {
+ final Builder builder = builder(fragments.size());
+
+ for (SimTraceFragment fragment : fragments) {
+ builder.add(fragment.timestamp + fragment.duration, fragment.usage, fragment.cores);
+ }
+
+ return builder.build();
+ }
+
+ /**
+ * Builder class for a {@link SimTrace}.
+ */
+ public static final class Builder {
+ private double[] usageCol;
+ private long[] deadlineCol;
+ private int[] coresCol;
+
+ private int size;
+ private boolean isBuilt;
+
+ /**
+ * Construct a new {@link Builder} instance.
+ */
+ private Builder(int initialCapacity) {
+ this.usageCol = new double[initialCapacity];
+ this.deadlineCol = new long[initialCapacity];
+ this.coresCol = new int[initialCapacity];
+ }
+
+ /**
+ * Add a fragment to the trace.
+ *
+ * @param deadline The timestamp at which the fragment ends (in epoch millis).
+ * @param usage The CPU usage at this fragment.
+ * @param cores The number of cores used during this fragment.
+ */
+ public void add(long deadline, double usage, int cores) {
+ if (isBuilt) {
+ recreate();
+ }
+
+ int size = this.size;
+ double[] usageCol = this.usageCol;
+
+ if (size == usageCol.length) {
+ grow();
+ usageCol = this.usageCol;
+ }
+
+ deadlineCol[size] = deadline;
+ usageCol[size] = usage;
+ coresCol[size] = cores;
+
+ this.size++;
+ }
+
+ /**
+ * Build the {@link SimTrace} instance.
+ */
+ public SimTrace build() {
+ isBuilt = true;
+ return new SimTrace(usageCol, deadlineCol, coresCol, size);
+ }
+
+ /**
+ * Helper method to grow the capacity of the trace.
+ */
+ private void grow() {
+ int arraySize = usageCol.length;
+ int newSize = arraySize + (arraySize >> 1);
+
+ usageCol = Arrays.copyOf(usageCol, newSize);
+ deadlineCol = Arrays.copyOf(deadlineCol, newSize);
+ coresCol = Arrays.copyOf(coresCol, newSize);
+ }
+
+ /**
+ * Clone the columns of the trace.
+ *
+ * <p>
+ * This is necessary when a {@link SimTrace} has been built already, but the user is again adding entries to
+ * the builder.
+ */
+ private void recreate() {
+ isBuilt = false;
+ usageCol = usageCol.clone();
+ deadlineCol = deadlineCol.clone();
+ coresCol = coresCol.clone();
+ }
+ }
+
+ /**
+ * Implementation of {@link SimWorkload} that executes a trace.
+ */
+ private static class Workload implements SimWorkload {
+ private WorkloadStageLogic logic;
+
+ private final long offset;
+ private final double[] usageCol;
+ private final long[] deadlineCol;
+ private final int[] coresCol;
+ private final int size;
+
+ private Workload(long offset, double[] usageCol, long[] deadlineCol, int[] coresCol, int size) {
+ this.offset = offset;
+ this.usageCol = usageCol;
+ this.deadlineCol = deadlineCol;
+ this.coresCol = coresCol;
+ this.size = size;
+ }
+
+ @Override
+ public void onStart(SimMachineContext ctx) {
+ final WorkloadStageLogic logic;
+ if (ctx.getCpus().size() == 1) {
+ logic = new SingleWorkloadLogic(ctx, offset, usageCol, deadlineCol, size);
+ } else {
+ logic = new MultiWorkloadLogic(ctx, offset, usageCol, deadlineCol, coresCol, size);
+ }
+ this.logic = logic;
+ }
+
+ @Override
+ public void onStop(SimMachineContext ctx) {
+ final WorkloadStageLogic logic = this.logic;
+
+ if (logic != null) {
+ this.logic = null;
+ logic.getStage().close();
+ }
+ }
+ }
+
+ /**
+ * Interface to represent the {@link FlowStage} that simulates the trace workload.
+ */
+ private interface WorkloadStageLogic extends FlowStageLogic {
+ /**
+ * Return the {@link FlowStage} belonging to this instance.
+ */
+ FlowStage getStage();
+ }
+
+ /**
+ * Implementation of {@link FlowStageLogic} for just a single CPU resource.
+ */
+ private static class SingleWorkloadLogic implements WorkloadStageLogic {
+ private final FlowStage stage;
+ private final OutPort output;
+ private int index;
+
+ private final long offset;
+ private final double[] usageCol;
+ private final long[] deadlineCol;
+ private final int size;
+
+ private final SimMachineContext ctx;
+
+ private SingleWorkloadLogic(
+ SimMachineContext ctx, long offset, double[] usageCol, long[] deadlineCol, int size) {
+ this.ctx = ctx;
+ this.offset = offset;
+ this.usageCol = usageCol;
+ this.deadlineCol = deadlineCol;
+ this.size = size;
+
+ final FlowGraph graph = ctx.getGraph();
+ final List<? extends SimProcessingUnit> cpus = ctx.getCpus();
+
+ stage = graph.newStage(this);
+
+ final SimProcessingUnit cpu = cpus.get(0);
+ final OutPort output = stage.getOutlet("cpu");
+ this.output = output;
+
+ graph.connect(output, cpu.getInput());
+ }
+
+ @Override
+ public long onUpdate(FlowStage ctx, long now) {
+ int size = this.size;
+ long offset = this.offset;
+ long nowOffset = now - offset;
+
+ int index = this.index;
+
+ long[] deadlines = deadlineCol;
+ long deadline = deadlines[index];
+
+ while (deadline <= nowOffset) {
+ if (++index >= size) {
+ return doStop(ctx);
+ }
+ deadline = deadlines[index];
+ }
+
+ this.index = index;
+ this.output.push((float) usageCol[index]);
+ return deadline + offset;
+ }
+
+ @Override
+ public FlowStage getStage() {
+ return stage;
+ }
+
+ /**
+ * Helper method to stop the execution of the workload.
+ */
+ private long doStop(FlowStage ctx) {
+ final SimMachineContext machineContext = this.ctx;
+ if (machineContext != null) {
+ machineContext.shutdown();
+ }
+ ctx.close();
+ return Long.MAX_VALUE;
+ }
+ }
+
+ /**
+ * Implementation of {@link FlowStageLogic} for multiple CPUs.
+ */
+ private static class MultiWorkloadLogic implements WorkloadStageLogic {
+ private final FlowStage stage;
+ private final OutPort[] outputs;
+ private int index;
+ private final int coreCount;
+
+ private final long offset;
+ private final double[] usageCol;
+ private final long[] deadlineCol;
+ private final int[] coresCol;
+ private final int size;
+
+ private final SimMachineContext ctx;
+
+ private MultiWorkloadLogic(
+ SimMachineContext ctx, long offset, double[] usageCol, long[] deadlineCol, int[] coresCol, int size) {
+ this.ctx = ctx;
+ this.offset = offset;
+ this.usageCol = usageCol;
+ this.deadlineCol = deadlineCol;
+ this.coresCol = coresCol;
+ this.size = size;
+
+ final FlowGraph graph = ctx.getGraph();
+ final List<? extends SimProcessingUnit> cpus = ctx.getCpus();
+
+ stage = graph.newStage(this);
+ coreCount = cpus.size();
+
+ final OutPort[] outputs = new OutPort[cpus.size()];
+ this.outputs = outputs;
+
+ for (int i = 0; i < cpus.size(); i++) {
+ final SimProcessingUnit cpu = cpus.get(i);
+ final OutPort output = stage.getOutlet("cpu" + i);
+
+ graph.connect(output, cpu.getInput());
+ outputs[i] = output;
+ }
+ }
+
+ @Override
+ public long onUpdate(FlowStage ctx, long now) {
+ int size = this.size;
+ long offset = this.offset;
+ long nowOffset = now - offset;
+
+ int index = this.index;
+
+ long[] deadlines = deadlineCol;
+ long deadline = deadlines[index];
+
+ while (deadline <= nowOffset && ++index < size) {
+ deadline = deadlines[index];
+ }
+
+ if (index >= size) {
+ final SimMachineContext machineContext = this.ctx;
+ if (machineContext != null) {
+ machineContext.shutdown();
+ }
+ ctx.close();
+ return Long.MAX_VALUE;
+ }
+
+ this.index = index;
+
+ int cores = Math.min(coreCount, coresCol[index]);
+ float usage = (float) usageCol[index] / cores;
+
+ final OutPort[] outputs = this.outputs;
+
+ for (int i = 0; i < cores; i++) {
+ outputs[i].push(usage);
+ }
+
+ for (int i = cores; i < outputs.length; i++) {
+ outputs[i].push(0.f);
+ }
+
+ return deadline + offset;
+ }
+
+ @Override
+ public FlowStage getStage() {
+ return stage;
+ }
+ }
+}
diff --git a/opendc-simulator/opendc-simulator-compute/src/main/java/org/opendc/simulator/compute/workload/SimTraceFragment.java b/opendc-simulator/opendc-simulator-compute/src/main/java/org/opendc/simulator/compute/workload/SimTraceFragment.java
new file mode 100644
index 00000000..12c1348d
--- /dev/null
+++ b/opendc-simulator/opendc-simulator-compute/src/main/java/org/opendc/simulator/compute/workload/SimTraceFragment.java
@@ -0,0 +1,94 @@
+/*
+ * Copyright (c) 2022 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 java.util.Objects;
+
+/**
+ * A fragment of the workload trace.
+ */
+public final class SimTraceFragment {
+ final long timestamp;
+ final long duration;
+ final double usage;
+ final int cores;
+
+ /**
+ * Construct a {@link SimTraceFragment}.
+ *
+ * @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 SimTraceFragment(long timestamp, long duration, double usage, int cores) {
+ this.timestamp = timestamp;
+ this.duration = duration;
+ this.usage = usage;
+ this.cores = cores;
+ }
+
+ /**
+ * Return the timestamp at which the fragment starts (in epoch millis).
+ */
+ public long getTimestamp() {
+ return timestamp;
+ }
+
+ /**
+ * Return the duration of the fragment (in milliseconds).
+ */
+ public long getDuration() {
+ return duration;
+ }
+
+ /**
+ * Return the CPU usage during the fragment (in MHz).
+ */
+ public double getUsage() {
+ return usage;
+ }
+
+ /**
+ * Return the amount of cores utilized during the fragment.
+ */
+ public int getCores() {
+ return cores;
+ }
+
+ @Override
+ public boolean equals(Object o) {
+ if (this == o) return true;
+ if (o == null || getClass() != o.getClass()) return false;
+ SimTraceFragment that = (SimTraceFragment) o;
+ return timestamp == that.timestamp
+ && duration == that.duration
+ && Double.compare(that.usage, usage) == 0
+ && cores == that.cores;
+ }
+
+ @Override
+ public int hashCode() {
+ return Objects.hash(timestamp, duration, usage, cores);
+ }
+}
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/java/org/opendc/simulator/compute/workload/SimWorkload.java
index 61c6e2ad..7be51265 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/java/org/opendc/simulator/compute/workload/SimWorkload.java
@@ -1,5 +1,5 @@
/*
- * Copyright (c) 2020 AtLarge Research
+ * Copyright (c) 2022 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,15 +20,16 @@
* SOFTWARE.
*/
-package org.opendc.simulator.compute.workload
+package org.opendc.simulator.compute.workload;
-import org.opendc.simulator.compute.SimMachineContext
+import org.opendc.simulator.compute.SimMachineContext;
/**
* A model that characterizes the runtime behavior of some particular workload.
*
+ * <p>
* Workloads are stateful objects that may be paused and resumed at a later moment. As such, be careful when using the
- * same [SimWorkload] from multiple contexts.
+ * same {@link SimWorkload} from multiple contexts.
*/
public interface SimWorkload {
/**
@@ -36,12 +37,12 @@ public interface SimWorkload {
*
* @param ctx The execution context in which the machine runs.
*/
- public fun onStart(ctx: SimMachineContext)
+ void onStart(SimMachineContext ctx);
/**
* This method is invoked when the workload is stopped.
*
* @param ctx The execution context in which the machine runs.
*/
- public fun onStop(ctx: SimMachineContext)
+ void onStop(SimMachineContext ctx);
}
diff --git a/opendc-simulator/opendc-simulator-power/src/main/kotlin/org/opendc/simulator/power/SimPowerSource.kt b/opendc-simulator/opendc-simulator-compute/src/main/java/org/opendc/simulator/compute/workload/SimWorkloadLifecycle.java
index 07e9f52e..f0e2561f 100644
--- a/opendc-simulator/opendc-simulator-power/src/main/kotlin/org/opendc/simulator/power/SimPowerSource.kt
+++ b/opendc-simulator/opendc-simulator-compute/src/main/java/org/opendc/simulator/compute/workload/SimWorkloadLifecycle.java
@@ -1,5 +1,5 @@
/*
- * Copyright (c) 2021 AtLarge Research
+ * Copyright (c) 2022 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,35 +20,41 @@
* SOFTWARE.
*/
-package org.opendc.simulator.power
+package org.opendc.simulator.compute.workload;
-import org.opendc.simulator.flow.FlowEngine
-import org.opendc.simulator.flow.FlowSink
+import java.util.HashSet;
+import org.opendc.simulator.compute.SimMachineContext;
/**
- * A [SimPowerOutlet] that represents a source of electricity.
- *
- * @param engine The underlying [FlowEngine] to drive the simulation under the hood.
+ * A helper class to manage the lifecycle of a {@link SimWorkload}.
*/
-public class SimPowerSource(engine: FlowEngine, public val capacity: Double) : SimPowerOutlet() {
- /**
- * The resource source that drives this power source.
- */
- private val source = FlowSink(engine, capacity)
+public final class SimWorkloadLifecycle {
+ private final SimMachineContext ctx;
+ private final HashSet<Runnable> waiting = new HashSet<>();
/**
- * The power draw at this instant.
+ * Construct a {@link SimWorkloadLifecycle} instance.
+ *
+ * @param ctx The {@link SimMachineContext} of the workload.
*/
- public val powerDraw: Double
- get() = source.rate
-
- override fun onConnect(inlet: SimPowerInlet) {
- source.startConsumer(inlet.createSource())
+ public SimWorkloadLifecycle(SimMachineContext ctx) {
+ this.ctx = ctx;
}
- override fun onDisconnect(inlet: SimPowerInlet) {
- source.cancel()
+ /**
+ * Register a "completer" callback that must be invoked before ending the lifecycle of the workload.
+ */
+ public Runnable newCompleter() {
+ Runnable completer = new Runnable() {
+ @Override
+ public void run() {
+ final HashSet<Runnable> waiting = SimWorkloadLifecycle.this.waiting;
+ if (waiting.remove(this) && waiting.isEmpty()) {
+ ctx.shutdown();
+ }
+ }
+ };
+ waiting.add(completer);
+ return completer;
}
-
- override fun toString(): String = "SimPowerSource"
}
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
deleted file mode 100644
index 71784567..00000000
--- a/opendc-simulator/opendc-simulator-compute/src/main/kotlin/org/opendc/simulator/compute/SimAbstractMachine.kt
+++ /dev/null
@@ -1,239 +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 mu.KotlinLogging
-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.flow.FlowConsumer
-import org.opendc.simulator.flow.FlowConvergenceListener
-import org.opendc.simulator.flow.FlowEngine
-import org.opendc.simulator.flow.FlowForwarder
-import org.opendc.simulator.flow.FlowSink
-import org.opendc.simulator.flow.FlowSource
-import org.opendc.simulator.flow.batch
-
-/**
- * Abstract implementation of the [SimMachine] interface.
- *
- * @param engine The engine to manage the machine's resources.
- * @param model The model of the machine.
- */
-public abstract class SimAbstractMachine(
- protected val engine: FlowEngine,
- 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 network interfaces available to the machine.
- */
- 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 }
-
- /**
- * The current active [Context].
- */
- private var _ctx: Context? = null
-
- override fun startWorkload(workload: SimWorkload, meta: Map<String, Any>): SimMachineContext {
- check(_ctx == null) { "A machine cannot run concurrently" }
-
- val ctx = Context(workload, meta)
- ctx.start()
- return ctx
- }
-
- override fun cancel() {
- _ctx?.close()
- }
-
- override fun onConverge(now: Long) {}
-
- /**
- * The execution context in which the workload runs.
- *
- * @param workload The workload that is running on the machine.
- * @param meta The metadata passed to the workload.
- */
- private inner class Context(
- private val workload: SimWorkload,
- override val meta: Map<String, Any>
- ) : SimMachineContext {
- /**
- * A flag to indicate that the context has been closed.
- */
- private var isClosed = false
-
- val engine: FlowEngine = this@SimAbstractMachine.engine
-
- /**
- * Start this context.
- */
- fun start() {
- try {
- _ctx = this
- engine.batch { workload.onStart(this) }
- } catch (cause: Throwable) {
- logger.warn(cause) { "Workload failed during onStart callback" }
- close()
- }
- }
-
- override val cpus: List<SimProcessingUnit> = this@SimAbstractMachine.cpus
-
- 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() {
- if (isClosed) {
- return
- }
-
- isClosed = true
- assert(_ctx == this) { "Invariant violation: multiple contexts active for a single machine" }
- _ctx = null
-
- // Cancel all the resources associated with the machine
- doCancel()
-
- try {
- workload.onStop(this)
- } catch (cause: Throwable) {
- logger.warn(cause) { "Workload failed during onStop callback" }
- }
- }
-
- /**
- * Run the stop procedures for the resources associated with the machine.
- */
- private fun doCancel() {
- engine.batch {
- for (cpu in cpus) {
- cpu.cancel()
- }
-
- memory.cancel()
-
- for (ifx in net) {
- (ifx as NetworkAdapterImpl).disconnect()
- }
-
- for (storage in storage) {
- val impl = storage as StorageDeviceImpl
- impl.read.cancel()
- impl.write.cancel()
- }
- }
- }
-
- override fun toString(): String = "SimAbstractMachine.Context"
- }
-
- /**
- * The [SimMemory] implementation for a machine.
- */
- private class Memory(source: FlowSink, override val models: List<MemoryUnit>) : SimMemory, FlowConsumer by source {
- override fun toString(): String = "SimAbstractMachine.Memory"
- }
-
- /**
- * The [SimNetworkAdapter] implementation for a machine.
- */
- private class NetworkAdapterImpl(
- engine: FlowEngine,
- model: NetworkAdapter,
- index: Int
- ) : SimNetworkAdapter(), SimNetworkInterface {
- override val name: String = "eth$index"
-
- override val bandwidth: Double = model.bandwidth
-
- override val provider: FlowConsumer
- get() = _rx
-
- override fun createConsumer(): FlowSource = _tx
-
- override val tx: FlowConsumer
- get() = _tx
- private val _tx = FlowForwarder(engine)
-
- override val rx: FlowSource
- get() = _rx
- private val _rx = FlowForwarder(engine)
-
- override fun toString(): String = "SimAbstractMachine.NetworkAdapterImpl[name=$name,bandwidth=$bandwidth]"
- }
-
- /**
- * The [SimStorageInterface] implementation for a machine.
- */
- private class StorageDeviceImpl(
- engine: FlowEngine,
- model: StorageDevice,
- index: Int
- ) : SimStorageInterface {
- override val name: String = "disk$index"
-
- 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]"
- }
-
- private companion object {
- /**
- * The logging instance associated with this class.
- */
- @JvmStatic
- private val logger = KotlinLogging.logger {}
- }
-}
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
deleted file mode 100644
index 4c824440..00000000
--- a/opendc-simulator/opendc-simulator-compute/src/main/kotlin/org/opendc/simulator/compute/SimBareMetalMachine.kt
+++ /dev/null
@@ -1,130 +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 org.opendc.simulator.compute.device.SimPsu
-import org.opendc.simulator.compute.model.MachineModel
-import org.opendc.simulator.compute.model.ProcessingUnit
-import org.opendc.simulator.compute.power.PowerDriver
-import org.opendc.simulator.flow.FlowConsumer
-import org.opendc.simulator.flow.FlowEngine
-import org.opendc.simulator.flow.FlowSink
-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].
- *
- * @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.
- */
-public class SimBareMetalMachine(
- engine: FlowEngine,
- model: MachineModel,
- powerDriver: PowerDriver,
- public val psu: SimPsu = SimPsu(500.0, mapOf(1.0 to 1.0))
-) : SimAbstractMachine(engine, model) {
- /**
- * The current power usage of the machine (without PSU loss) in W.
- */
- public val powerUsage: Double
- get() = _powerUsage
- private var _powerUsage = 0.0
-
- /**
- * The total energy usage of the machine (without PSU loss) in Joules.
- */
- public val energyUsage: Double
- get() {
- computeEnergyUsage(engine.clock.millis())
- return _energyUsage
- }
- private var _energyUsage = 0.0
- private var _energyLastComputation = 0L
-
- /**
- * The processing units of the machine.
- */
- override val cpus: List<SimProcessingUnit> = model.cpus.map { cpu ->
- Cpu(FlowSink(engine, cpu.frequency, this@SimBareMetalMachine), cpu)
- }
-
- /**
- * The logic of the power driver.
- */
- private val powerDriverLogic = powerDriver.createLogic(this, cpus)
-
- private var _lastConverge = Long.MAX_VALUE
-
- override fun onConverge(now: Long) {
- // Update the PSU stage
- psu.update()
-
- val lastConverge = _lastConverge
- _lastConverge = now
- val duration = max(0, now - lastConverge)
- if (duration > 0) {
- // Compute the power and energy usage of the machine
- computeEnergyUsage(now)
- }
-
- _powerUsage = powerDriverLogic.computePower()
- }
-
- init {
- psu.connect(powerDriverLogic)
- _powerUsage = powerDriverLogic.computePower()
- }
-
- /**
- * Helper method to compute total energy usage.
- */
- private fun computeEnergyUsage(now: Long) {
- val duration = max(0, now - _energyLastComputation)
- _energyLastComputation = now
-
- // Compute the energy usage of the machine
- _energyUsage += _powerUsage * (duration / 1000.0)
- }
-
- /**
- * A [SimProcessingUnit] of a bare-metal machine.
- */
- 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/device/SimPsu.kt b/opendc-simulator/opendc-simulator-compute/src/main/kotlin/org/opendc/simulator/compute/device/SimPsu.kt
deleted file mode 100644
index 3d3703ae..00000000
--- a/opendc-simulator/opendc-simulator-compute/src/main/kotlin/org/opendc/simulator/compute/device/SimPsu.kt
+++ /dev/null
@@ -1,114 +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.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.TreeMap
-
-/**
- * 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) {
- _ctx = null
- }
-
- override fun onPull(conn: FlowConnection, now: Long): Long {
- val powerDraw = computePowerDraw(_driver?.computePower() ?: 0.0)
- conn.push(powerDraw)
- return Long.MAX_VALUE
- }
-
- override fun onConverge(conn: FlowConnection, now: 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/kernel/SimHypervisor.kt b/opendc-simulator/opendc-simulator-compute/src/main/kotlin/org/opendc/simulator/compute/kernel/SimHypervisor.kt
deleted file mode 100644
index e1486d71..00000000
--- a/opendc-simulator/opendc-simulator-compute/src/main/kotlin/org/opendc/simulator/compute/kernel/SimHypervisor.kt
+++ /dev/null
@@ -1,442 +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.kernel
-
-import org.opendc.simulator.compute.SimAbstractMachine
-import org.opendc.simulator.compute.SimMachine
-import org.opendc.simulator.compute.SimMachineContext
-import org.opendc.simulator.compute.SimProcessingUnit
-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.kernel.interference.VmInterferenceMember
-import org.opendc.simulator.compute.kernel.interference.VmInterferenceProfile
-import org.opendc.simulator.compute.model.MachineModel
-import org.opendc.simulator.compute.model.ProcessingUnit
-import org.opendc.simulator.compute.workload.SimWorkload
-import org.opendc.simulator.flow.FlowConsumer
-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.FlowMultiplexerFactory
-import java.util.SplittableRandom
-import kotlin.math.roundToLong
-
-/**
- * A SimHypervisor facilitates the execution of multiple concurrent [SimWorkload]s, while acting as a single workload
- * to another [SimMachine].
- *
- * @param engine The [FlowEngine] to drive the simulation.
- * @param muxFactory The factor for the [FlowMultiplexer] to multiplex the workloads.
- * @param random A randomness generator for the interference calculations.
- * @param scalingGovernor The scaling governor to use for scaling the CPU frequency of the underlying hardware.
- * @param interferenceDomain The interference domain to which the hypervisor belongs.
- */
-public class SimHypervisor(
- private val engine: FlowEngine,
- muxFactory: FlowMultiplexerFactory,
- private val random: SplittableRandom,
- private val scalingGovernor: ScalingGovernor? = null,
- private val interferenceDomain: VmInterferenceDomain = VmInterferenceDomain()
-) : SimWorkload, FlowConvergenceListener {
- /**
- * The [FlowMultiplexer] to multiplex the virtual machines.
- */
- private val mux = muxFactory.newMultiplexer(engine, this)
-
- /**
- * The virtual machines running on this hypervisor.
- */
- private val _vms = mutableSetOf<VirtualMachine>()
- public val vms: Set<SimMachine>
- get() = _vms
-
- /**
- * The resource counters associated with the hypervisor.
- */
- public val counters: SimHypervisorCounters
- get() = _counters
- private val _counters = CountersImpl(this)
-
- /**
- * The CPU capacity of the hypervisor in MHz.
- */
- public val cpuCapacity: Double
- get() = mux.capacity
-
- /**
- * The CPU demand of the hypervisor in MHz.
- */
- public val cpuDemand: Double
- get() = mux.demand
-
- /**
- * The CPU usage of the hypervisor in MHz.
- */
- public val cpuUsage: Double
- get() = mux.rate
-
- /**
- * The machine on which the hypervisor runs.
- */
- private lateinit var context: SimMachineContext
-
- /**
- * The scaling governors attached to the physical CPUs backing this hypervisor.
- */
- private val governors = mutableListOf<ScalingGovernor.Logic>()
-
- /* SimHypervisor */
- /**
- * Create a [SimMachine] instance on which users may run a [SimWorkload].
- *
- * @param model The machine to create.
- */
- public fun newMachine(model: MachineModel): SimVirtualMachine {
- require(canFit(model)) { "Machine does not fit" }
- val vm = VirtualMachine(model)
- _vms.add(vm)
- return vm
- }
-
- /**
- * Remove the specified [machine] from the hypervisor.
- *
- * @param machine The machine to remove.
- */
- public fun removeMachine(machine: SimVirtualMachine) {
- if (_vms.remove(machine)) {
- // This cast must always succeed, since `_vms` only contains `VirtualMachine` types.
- (machine as VirtualMachine).close()
- }
- }
-
- /**
- * Determine whether the specified machine characterized by [model] can fit on this hypervisor at this moment.
- */
- public fun canFit(model: MachineModel): Boolean {
- return (mux.maxInputs - mux.inputs.size) >= model.cpus.size
- }
-
- /* 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())
- }
- }
-
- override fun onStop(ctx: SimMachineContext) {}
-
- private var _cpuCount = 0
- private var _cpuCapacity = 0.0
- private var _lastConverge = engine.clock.millis()
-
- /* FlowConvergenceListener */
- override fun onConverge(now: Long) {
- val lastConverge = _lastConverge
- _lastConverge = now
- val delta = now - lastConverge
-
- if (delta > 0) {
- _counters.record()
-
- val mux = mux
- val load = mux.rate / mux.capacity.coerceAtLeast(1.0)
- val random = random
-
- for (vm in _vms) {
- vm._counters.record(random, load)
- }
- }
-
- val load = cpuDemand / cpuCapacity
- for (governor in governors) {
- governor.onLimit(load)
- }
- }
-
- /**
- * A virtual machine running on the hypervisor.
- *
- * @param model The machine model of the virtual machine.
- */
- private inner class VirtualMachine(model: MachineModel) : SimAbstractMachine(engine, model), SimVirtualMachine, AutoCloseable {
- /**
- * A flag to indicate that the machine is closed.
- */
- private var isClosed = false
-
- /**
- * The vCPUs of the machine.
- */
- override val cpus = model.cpus.map { cpu -> VCpu(mux, mux.newInput(cpu.frequency), cpu) }
-
- /**
- * The resource counters associated with the hypervisor.
- */
- override val counters: SimHypervisorCounters
- get() = _counters
-
- @JvmField val _counters = VmCountersImpl(cpus, null)
-
- /**
- * 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 startWorkload(workload: SimWorkload, meta: Map<String, Any>): SimMachineContext {
- check(!isClosed) { "Machine is closed" }
-
- val profile = meta["interference-profile"] as? VmInterferenceProfile
- val interferenceMember = if (profile != null) interferenceDomain.join(profile) else null
-
- val counters = _counters
- counters.member = interferenceMember
-
- return super.startWorkload(
- object : SimWorkload {
- override fun onStart(ctx: SimMachineContext) {
- try {
- interferenceMember?.activate()
- workload.onStart(ctx)
- } catch (cause: Throwable) {
- interferenceMember?.deactivate()
- throw cause
- }
- }
-
- override fun onStop(ctx: SimMachineContext) {
- interferenceMember?.deactivate()
- counters.member = null
- workload.onStop(ctx)
- }
- },
- meta
- )
- }
-
- override fun close() {
- if (isClosed) {
- return
- }
-
- isClosed = true
- cancel()
-
- for (cpu in cpus) {
- cpu.close()
- }
- }
- }
-
- /**
- * 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(_) = TODO("Capacity changes on vCPU not supported")
-
- override fun toString(): String = "SimAbstractHypervisor.VCpu[model=$model]"
-
- /**
- * Close the CPU
- */
- fun close() {
- switch.removeInput(source)
- }
-
- fun flush() {
- switch.flushCounters(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: SimHypervisor) : 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]
-
- val _cpuTime = LongArray(4)
- private val _previous = DoubleArray(3)
-
- /**
- * 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 demandDelta = demand - previous[0]
- val actualDelta = actual - previous[1]
- val remainingDelta = remaining - previous[2]
-
- previous[0] = demand
- previous[1] = actual
- previous[2] = remaining
-
- cpuTime[0] += (actualDelta * d).roundToLong()
- cpuTime[1] += (remainingDelta * d).roundToLong()
- cpuTime[2] += ((demandDelta - actualDelta) * d).roundToLong()
- }
-
- override fun flush() {
- hv.mux.flushCounters()
- record()
- }
- }
-
- /**
- * A [SimHypervisorCounters] implementation for a virtual machine.
- */
- private inner class VmCountersImpl(
- private val cpus: List<VCpu>,
- @JvmField var member: VmInterferenceMember?
- ) : SimHypervisorCounters {
- private val d = cpus.size / cpus.sumOf { it.model.frequency } * 1000
-
- 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(3)
-
- /**
- * Record the CPU time of the hypervisor.
- */
- fun record(random: SplittableRandom, load: Double) {
- val cpuTime = _cpuTime
- val previous = _previous
-
- var demand = 0.0
- var actual = 0.0
- var remaining = 0.0
-
- for (cpu in cpus) {
- val counters = cpu.counters
-
- actual += counters.actual
- demand += counters.demand
- remaining += counters.remaining
- }
-
- val demandDelta = demand - previous[0]
- val actualDelta = actual - previous[1]
- val remainingDelta = remaining - previous[2]
-
- previous[0] = demand
- previous[1] = actual
- previous[2] = remaining
-
- val d = d
- cpuTime[0] += (actualDelta * d).roundToLong()
- cpuTime[1] += (remainingDelta * d).roundToLong()
- cpuTime[2] += ((demandDelta - actualDelta) * d).roundToLong()
-
- // Compute the performance penalty due to flow interference
- val member = member
- if (member != null) {
- val penalty = 1 - member.apply(random, load)
- val interference = (actualDelta * d * penalty).roundToLong()
-
- if (interference > 0) {
- cpuTime[3] += interference
- _counters._cpuTime[3] += interference
- }
- }
- }
-
- override fun flush() {
- for (cpu in cpus) {
- cpu.flush()
- }
- }
- }
-}
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
deleted file mode 100644
index 1a03221d..00000000
--- a/opendc-simulator/opendc-simulator-compute/src/main/kotlin/org/opendc/simulator/compute/kernel/cpufreq/ConservativeScalingGovernor.kt
+++ /dev/null
@@ -1,66 +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.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
deleted file mode 100644
index aef15ce9..00000000
--- a/opendc-simulator/opendc-simulator-compute/src/main/kotlin/org/opendc/simulator/compute/kernel/cpufreq/OnDemandScalingGovernor.kt
+++ /dev/null
@@ -1,50 +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.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/kernel/cpufreq/PerformanceScalingGovernor.kt b/opendc-simulator/opendc-simulator-compute/src/main/kotlin/org/opendc/simulator/compute/kernel/cpufreq/PerformanceScalingGovernor.kt
deleted file mode 100644
index 13109a9a..00000000
--- a/opendc-simulator/opendc-simulator-compute/src/main/kotlin/org/opendc/simulator/compute/kernel/cpufreq/PerformanceScalingGovernor.kt
+++ /dev/null
@@ -1,36 +0,0 @@
-/*
- * Copyright (c) 2021 AtLarge Research
- *
- * Permission is hereby granted, free of charge, to any person obtaining a copy
- * of this software and associated documentation files (the "Software"), to deal
- * in the Software without restriction, including without limitation the rights
- * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
- * copies of the Software, and to permit persons to whom the Software is
- * furnished to do so, subject to the following conditions:
- *
- * The above copyright notice and this permission notice shall be included in all
- * copies or substantial portions of the Software.
- *
- * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
- * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
- * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
- * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
- * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
- * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
- * SOFTWARE.
- */
-
-package org.opendc.simulator.compute.kernel.cpufreq
-
-/**
- * A CPUFreq [ScalingGovernor] that causes the highest possible frequency to be requested from the resource.
- */
-public class PerformanceScalingGovernor : ScalingGovernor {
- override fun createLogic(policy: ScalingPolicy): ScalingGovernor.Logic = object : ScalingGovernor.Logic {
- override fun onStart() {
- policy.target = policy.max
- }
- }
-
- override fun toString(): String = "PerformanceScalingGovernor"
-}
diff --git a/opendc-simulator/opendc-simulator-compute/src/main/kotlin/org/opendc/simulator/compute/kernel/interference/VmInterferenceDomain.kt b/opendc-simulator/opendc-simulator-compute/src/main/kotlin/org/opendc/simulator/compute/kernel/interference/VmInterferenceDomain.kt
deleted file mode 100644
index 6861823b..00000000
--- a/opendc-simulator/opendc-simulator-compute/src/main/kotlin/org/opendc/simulator/compute/kernel/interference/VmInterferenceDomain.kt
+++ /dev/null
@@ -1,131 +0,0 @@
-/*
- * Copyright (c) 2022 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 java.util.ArrayDeque
-import java.util.ArrayList
-import java.util.WeakHashMap
-
-/**
- * A domain where virtual machines may incur performance variability due to operating on the same resource and
- * therefore causing interference.
- */
-public class VmInterferenceDomain {
- /**
- * A cache to maintain a mapping between the active profiles in this domain.
- */
- private val cache = WeakHashMap<VmInterferenceProfile, VmInterferenceMember>()
-
- /**
- * The set of members active in this domain.
- */
- private val activeKeys = ArrayList<VmInterferenceMember>()
-
- /**
- * Queue of participants that will be removed or added to the active groups.
- */
- private val participants = ArrayDeque<VmInterferenceMember>()
-
- /**
- * Join this interference domain with the specified [profile] and return the [VmInterferenceMember] associated with
- * the profile. If the member does not exist, it will be created.
- */
- public fun join(profile: VmInterferenceProfile): VmInterferenceMember {
- return cache.computeIfAbsent(profile) { key -> key.newMember(this) }
- }
-
- /**
- * Mark the specified [member] as active in this interference domain.
- */
- internal fun activate(member: VmInterferenceMember) {
- val activeKeys = activeKeys
- val pos = activeKeys.binarySearch(member)
- if (pos < 0) {
- activeKeys.add(-pos - 1, member)
- }
-
- computeActiveGroups(activeKeys, member)
- }
-
- /**
- * Mark the specified [member] as inactive in this interference domain.
- */
- internal fun deactivate(member: VmInterferenceMember) {
- val activeKeys = activeKeys
- activeKeys.remove(member)
- computeActiveGroups(activeKeys, member)
- }
-
- /**
- * (Re-)compute the active groups.
- */
- private fun computeActiveGroups(activeKeys: ArrayList<VmInterferenceMember>, member: VmInterferenceMember) {
- if (activeKeys.isEmpty()) {
- return
- }
-
- val groups = member.membership
- val members = member.members
- val participants = participants
-
- for (group in groups) {
- val groupMembers = members[group]
-
- var i = 0
- var j = 0
- var intersection = 0
-
- // Compute the intersection of the group members and the current active members
- while (i < groupMembers.size && j < activeKeys.size) {
- val l = groupMembers[i]
- val rightEntry = activeKeys[j]
- val r = rightEntry.id
-
- if (l < r) {
- i++
- } else if (l > r) {
- j++
- } else {
- if (++intersection > 1) {
- rightEntry.addGroup(group)
- } else {
- participants.add(rightEntry)
- }
-
- i++
- j++
- }
- }
-
- while (true) {
- val participant = participants.poll() ?: break
-
- if (intersection <= 1) {
- participant.removeGroup(group)
- } else {
- participant.addGroup(group)
- }
- }
- }
- }
-}
diff --git a/opendc-simulator/opendc-simulator-compute/src/main/kotlin/org/opendc/simulator/compute/kernel/interference/VmInterferenceMember.kt b/opendc-simulator/opendc-simulator-compute/src/main/kotlin/org/opendc/simulator/compute/kernel/interference/VmInterferenceMember.kt
deleted file mode 100644
index 4b56a058..00000000
--- a/opendc-simulator/opendc-simulator-compute/src/main/kotlin/org/opendc/simulator/compute/kernel/interference/VmInterferenceMember.kt
+++ /dev/null
@@ -1,163 +0,0 @@
-/*
- * Copyright (c) 2022 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 java.util.SplittableRandom
-
-/**
- * A participant of an interference domain.
- */
-public class VmInterferenceMember(
- private val domain: VmInterferenceDomain,
- private val model: VmInterferenceModel,
- @JvmField internal val id: Int,
- @JvmField internal val membership: IntArray,
- @JvmField internal val members: Array<IntArray>,
- private val targets: DoubleArray,
- private val scores: DoubleArray
-) : Comparable<VmInterferenceMember> {
- /**
- * The active groups to which the key belongs.
- */
- private var groups: IntArray = IntArray(2)
- private var groupsSize: Int = 0
-
- /**
- * The number of users of the interference key.
- */
- private var refCount: Int = 0
-
- /**
- * Mark this member as active in this interference domain.
- */
- public fun activate() {
- if (refCount++ <= 0) {
- domain.activate(this)
- }
- }
-
- /**
- * Mark this member as inactive in this interference domain.
- */
- public fun deactivate() {
- if (--refCount <= 0) {
- domain.deactivate(this)
- }
- }
-
- /**
- * Compute the performance score of the member in this interference domain.
- *
- * @param random The source of randomness to apply when computing the performance score.
- * @param load The overall load on the interference domain.
- * @return A score representing the performance score to be applied to the member, with 1
- * meaning no influence, <1 means that performance degrades, and >1 means that performance improves.
- */
- public fun apply(random: SplittableRandom, load: Double): Double {
- val groupsSize = groupsSize
-
- if (groupsSize == 0) {
- return 1.0
- }
-
- val groups = groups
- val targets = targets
-
- var low = 0
- var high = groupsSize - 1
- var group = -1
-
- // Perform binary search over the groups based on target load
- while (low <= high) {
- val mid = low + high ushr 1
- val midGroup = groups[mid]
- val target = targets[midGroup]
-
- if (target < load) {
- low = mid + 1
- group = midGroup
- } else if (target > load) {
- high = mid - 1
- } else {
- group = midGroup
- break
- }
- }
-
- return if (group >= 0 && random.nextInt(members[group].size) == 0) {
- scores[group]
- } else {
- 1.0
- }
- }
-
- /**
- * Add an active group to this member.
- */
- internal fun addGroup(group: Int) {
- var groups = groups
- val groupsSize = groupsSize
- val pos = groups.binarySearch(group, toIndex = groupsSize)
-
- if (pos >= 0) {
- return
- }
-
- val idx = -pos - 1
-
- if (groups.size == groupsSize) {
- val newSize = groupsSize + (groupsSize shr 1)
- groups = groups.copyOf(newSize)
- this.groups = groups
- }
-
- groups.copyInto(groups, idx + 1, idx, groupsSize)
- groups[idx] = group
- this.groupsSize += 1
- }
-
- /**
- * Remove an active group from this member.
- */
- internal fun removeGroup(group: Int) {
- val groups = groups
- val groupsSize = groupsSize
- val pos = groups.binarySearch(group, toIndex = groupsSize)
-
- if (pos < 0) {
- return
- }
-
- groups.copyInto(groups, pos, pos + 1, groupsSize)
- this.groupsSize -= 1
- }
-
- override fun compareTo(other: VmInterferenceMember): Int {
- val cmp = model.hashCode().compareTo(other.model.hashCode())
- if (cmp != 0) {
- return cmp
- }
-
- return id.compareTo(other.id)
- }
-}
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
deleted file mode 100644
index 238bffc0..00000000
--- a/opendc-simulator/opendc-simulator-compute/src/main/kotlin/org/opendc/simulator/compute/kernel/interference/VmInterferenceModel.kt
+++ /dev/null
@@ -1,191 +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.kernel.interference
-
-import java.util.TreeMap
-import java.util.TreeSet
-
-/**
- * An interference model that models the resource interference between virtual machines on a host.
- *
- * @param members The target load of each group.
- * @param scores The performance score of each group.
- * @param members The members belonging to each group.
- * @param membership The identifier of each key.
- * @param size The number of groups.
- */
-public class VmInterferenceModel private constructor(
- private val idMapping: Map<String, Int>,
- private val members: Array<IntArray>,
- private val membership: Array<IntArray>,
- private val targets: DoubleArray,
- private val scores: DoubleArray,
- private val size: Int
-) {
- /**
- * Return the [VmInterferenceProfile] associated with the specified [id].
- *
- * @param id The identifier of the virtual machine.
- * @return A [VmInterferenceProfile] representing the virtual machine as part of interference model or `null` if
- * there is no profile for the virtual machine.
- */
- public fun getProfile(id: String): VmInterferenceProfile? {
- val intId = idMapping[id] ?: return null
- return VmInterferenceProfile(this, intId, membership[intId], members, targets, scores)
- }
-
- public companion object {
- /**
- * Construct a [Builder] instance.
- */
- @JvmStatic
- public fun builder(): Builder = Builder()
- }
-
- /**
- * Builder class for a [VmInterferenceModel]
- */
- public class Builder internal constructor() {
- /**
- * The target load of each group.
- */
- private var _targets = DoubleArray(INITIAL_CAPACITY) { Double.POSITIVE_INFINITY }
-
- /**
- * The performance score of each group.
- */
- private var _scores = DoubleArray(INITIAL_CAPACITY) { Double.POSITIVE_INFINITY }
-
- /**
- * The members of each group.
- */
- private var _members = ArrayList<Set<String>>(INITIAL_CAPACITY)
-
- /**
- * The mapping from member to group id.
- */
- private val ids = TreeSet<String>()
-
- /**
- * The number of groups in the model.
- */
- private var size = 0
-
- /**
- * Add the specified group to the model.
- */
- public fun addGroup(members: Set<String>, targetLoad: Double, score: Double): Builder {
- val size = size
-
- if (size == _targets.size) {
- grow()
- }
-
- _targets[size] = targetLoad
- _scores[size] = score
- _members.add(members)
- ids.addAll(members)
-
- this.size++
-
- return this
- }
-
- /**
- * Build the [VmInterferenceModel].
- */
- public fun build(): VmInterferenceModel {
- val size = size
- val targets = _targets
- val scores = _scores
- val members = _members
-
- val indices = IntArray(size) { it }
- indices.sortedWith(
- Comparator { l, r ->
- var cmp = targets[l].compareTo(targets[r]) // Order by target load
- if (cmp != 0) {
- return@Comparator cmp
- }
-
- cmp = scores[l].compareTo(scores[r]) // Higher penalty first (this means lower performance score first)
- if (cmp != 0) {
- cmp
- } else {
- l.compareTo(r)
- }
- }
- )
-
- val newTargets = DoubleArray(size)
- val newScores = DoubleArray(size)
- val newMembers = arrayOfNulls<IntArray>(size)
-
- var nextId = 0
- val idMapping = ids.associateWith { nextId++ }
- val membership = ids.associateWithTo(TreeMap()) { ArrayList<Int>() }
-
- for ((group, j) in indices.withIndex()) {
- newTargets[group] = targets[j]
- newScores[group] = scores[j]
- val groupMembers = members[j]
- val newGroupMembers = groupMembers.map { idMapping.getValue(it) }.toIntArray()
-
- newGroupMembers.sort()
- newMembers[group] = newGroupMembers
-
- for (member in groupMembers) {
- membership.getValue(member).add(group)
- }
- }
-
- @Suppress("UNCHECKED_CAST")
- return VmInterferenceModel(
- idMapping,
- newMembers as Array<IntArray>,
- membership.map { it.value.toIntArray() }.toTypedArray(),
- newTargets,
- newScores,
- size
- )
- }
-
- /**
- * Helper function to grow the capacity of the internal arrays.
- */
- private fun grow() {
- val oldSize = _targets.size
- val newSize = oldSize + (oldSize shr 1)
-
- _targets = _targets.copyOf(newSize)
- _scores = _scores.copyOf(newSize)
- }
-
- private companion object {
- /**
- * The initial capacity of the builder.
- */
- const val INITIAL_CAPACITY = 256
- }
- }
-}
diff --git a/opendc-simulator/opendc-simulator-compute/src/main/kotlin/org/opendc/simulator/compute/model/MachineModel.kt b/opendc-simulator/opendc-simulator-compute/src/main/kotlin/org/opendc/simulator/compute/model/MachineModel.kt
deleted file mode 100644
index 22dcaef4..00000000
--- a/opendc-simulator/opendc-simulator-compute/src/main/kotlin/org/opendc/simulator/compute/model/MachineModel.kt
+++ /dev/null
@@ -1,54 +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.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 MachineModel(
- public val cpus: List<ProcessingUnit>,
- public val memory: List<MemoryUnit>,
- public val net: List<NetworkAdapter> = emptyList(),
- public val storage: List<StorageDevice> = emptyList()
-) {
- /**
- * Optimize the [MachineModel] by merging all resources of the same type into a single resource with the combined
- * capacity. Such configurations can be simulated more efficiently by OpenDC.
- */
- public fun optimize(): MachineModel {
- val originalCpu = cpus[0]
- val freq = cpus.sumOf { it.frequency }
- val processingNode = originalCpu.node.copy(coreCount = 1)
- val processingUnits = listOf(originalCpu.copy(frequency = freq, node = processingNode))
-
- val memorySize = memory.sumOf { it.size }
- val memoryUnits = listOf(MemoryUnit("Generic", "Generic", 3200.0, memorySize))
-
- return MachineModel(processingUnits, memoryUnits)
- }
-}
diff --git a/opendc-simulator/opendc-simulator-compute/src/main/kotlin/org/opendc/simulator/compute/model/MemoryUnit.kt b/opendc-simulator/opendc-simulator-compute/src/main/kotlin/org/opendc/simulator/compute/model/MemoryUnit.kt
deleted file mode 100644
index bcbde5b1..00000000
--- a/opendc-simulator/opendc-simulator-compute/src/main/kotlin/org/opendc/simulator/compute/model/MemoryUnit.kt
+++ /dev/null
@@ -1,38 +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.model
-
-/**
- * A memory unit of a compute resource, either virtual or physical.
- *
- * @property vendor The vendor string of the memory.
- * @property modelName The name of the memory model.
- * @property speed The access speed of the memory in MHz.
- * @property size The size of the memory unit in MBs.
- */
-public data class MemoryUnit(
- public val vendor: String,
- public val modelName: String,
- public val speed: Double,
- public val size: Long
-)
diff --git a/opendc-simulator/opendc-simulator-compute/src/main/kotlin/org/opendc/simulator/compute/model/NetworkAdapter.kt b/opendc-simulator/opendc-simulator-compute/src/main/kotlin/org/opendc/simulator/compute/model/NetworkAdapter.kt
deleted file mode 100644
index 46472144..00000000
--- a/opendc-simulator/opendc-simulator-compute/src/main/kotlin/org/opendc/simulator/compute/model/NetworkAdapter.kt
+++ /dev/null
@@ -1,36 +0,0 @@
-/*
- * Copyright (c) 2021 AtLarge Research
- *
- * Permission is hereby granted, free of charge, to any person obtaining a copy
- * of this software and associated documentation files (the "Software"), to deal
- * in the Software without restriction, including without limitation the rights
- * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
- * copies of the Software, and to permit persons to whom the Software is
- * furnished to do so, subject to the following conditions:
- *
- * The above copyright notice and this permission notice shall be included in all
- * copies or substantial portions of the Software.
- *
- * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
- * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
- * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
- * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
- * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
- * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
- * SOFTWARE.
- */
-
-package org.opendc.simulator.compute.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/ProcessingNode.kt b/opendc-simulator/opendc-simulator-compute/src/main/kotlin/org/opendc/simulator/compute/model/ProcessingNode.kt
deleted file mode 100644
index 58ed816c..00000000
--- a/opendc-simulator/opendc-simulator-compute/src/main/kotlin/org/opendc/simulator/compute/model/ProcessingNode.kt
+++ /dev/null
@@ -1,38 +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.model
-
-/**
- * A processing node/package/socket containing possibly several CPU cores.
- *
- * @property vendor The vendor string of the processor node.
- * @property modelName The name of the processor node.
- * @property arch The micro-architecture of the processor node.
- * @property coreCount The number of logical CPUs in the processor node.
- */
-public data class ProcessingNode(
- public val vendor: String,
- public val arch: String,
- public val modelName: String,
- public val coreCount: Int
-)
diff --git a/opendc-simulator/opendc-simulator-compute/src/main/kotlin/org/opendc/simulator/compute/model/ProcessingUnit.kt b/opendc-simulator/opendc-simulator-compute/src/main/kotlin/org/opendc/simulator/compute/model/ProcessingUnit.kt
deleted file mode 100644
index 415e95e6..00000000
--- a/opendc-simulator/opendc-simulator-compute/src/main/kotlin/org/opendc/simulator/compute/model/ProcessingUnit.kt
+++ /dev/null
@@ -1,36 +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.model
-
-/**
- * A single logical compute unit of processor node, either virtual or physical.
- *
- * @property node The processing node containing the CPU core.
- * @property id The identifier of the CPU core within the processing node.
- * @property frequency The clock rate of the CPU in MHz.
- */
-public data class ProcessingUnit(
- public val node: ProcessingNode,
- public val id: Int,
- public val frequency: Double
-)
diff --git a/opendc-simulator/opendc-simulator-compute/src/main/kotlin/org/opendc/simulator/compute/model/StorageDevice.kt b/opendc-simulator/opendc-simulator-compute/src/main/kotlin/org/opendc/simulator/compute/model/StorageDevice.kt
deleted file mode 100644
index 2621ad6d..00000000
--- a/opendc-simulator/opendc-simulator-compute/src/main/kotlin/org/opendc/simulator/compute/model/StorageDevice.kt
+++ /dev/null
@@ -1,40 +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.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
deleted file mode 100644
index 46c397fe..00000000
--- a/opendc-simulator/opendc-simulator-compute/src/main/kotlin/org/opendc/simulator/compute/power/AsymptoticPowerModel.kt
+++ /dev/null
@@ -1,54 +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.power
-
-import kotlin.math.E
-import kotlin.math.pow
-
-/**
- * The asymptotic power model partially adapted from GreenCloud.
- *
- * @param maxPower The maximum power draw of the server in W.
- * @param idlePower The power draw of the server at its lowest utilization level in W.
- * @param asymUtil A utilization level at which the server attains asymptotic,
- * i.e., close to linear power consumption versus the offered load.
- * For most of the CPUs,a is in [0.2, 0.5].
- * @param isDvfsEnabled A flag indicates whether DVFS is enabled.
- */
-public class AsymptoticPowerModel(
- private val maxPower: Double,
- private val idlePower: Double,
- private val asymUtil: Double,
- private val isDvfsEnabled: Boolean
-) : PowerModel {
- private val factor: Double = (maxPower - idlePower) / 100
-
- public override fun computePower(utilization: Double): Double =
- if (isDvfsEnabled) {
- idlePower + (factor * 100) / 2 * (1 + utilization.pow(3) - E.pow(-utilization.pow(3) / asymUtil))
- } else {
- idlePower + (factor * 100) / 2 * (1 + utilization - E.pow(-utilization / asymUtil))
- }
-
- override fun toString(): String = "AsymptoticPowerModel[max=$maxPower,idle=$idlePower,asymptotic=$asymUtil]"
-}
diff --git a/opendc-simulator/opendc-simulator-compute/src/main/kotlin/org/opendc/simulator/compute/power/CubicPowerModel.kt b/opendc-simulator/opendc-simulator-compute/src/main/kotlin/org/opendc/simulator/compute/power/CubicPowerModel.kt
deleted file mode 100644
index 0d3bf6cc..00000000
--- a/opendc-simulator/opendc-simulator-compute/src/main/kotlin/org/opendc/simulator/compute/power/CubicPowerModel.kt
+++ /dev/null
@@ -1,41 +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.power
-
-import kotlin.math.pow
-
-/**
- * The cubic power model partially adapted from CloudSim.
- *
- * @param maxPower The maximum power draw of the server in W.
- * @param idlePower The power draw of the server at its lowest utilization level in W.
- */
-public class CubicPowerModel(private val maxPower: Double, private val idlePower: Double) : PowerModel {
- private val factor: Double = (maxPower - idlePower) / 100.0.pow(3)
-
- public override fun computePower(utilization: Double): Double {
- return idlePower + factor * (utilization * 100).pow(3)
- }
-
- override fun toString(): String = "CubicPowerModel[max=$maxPower,idle=$idlePower]"
-}
diff --git a/opendc-simulator/opendc-simulator-compute/src/main/kotlin/org/opendc/simulator/compute/power/InterpolationPowerModel.kt b/opendc-simulator/opendc-simulator-compute/src/main/kotlin/org/opendc/simulator/compute/power/InterpolationPowerModel.kt
deleted file mode 100644
index b17b87a9..00000000
--- a/opendc-simulator/opendc-simulator-compute/src/main/kotlin/org/opendc/simulator/compute/power/InterpolationPowerModel.kt
+++ /dev/null
@@ -1,64 +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.power
-
-import kotlin.math.ceil
-import kotlin.math.floor
-import kotlin.math.max
-import kotlin.math.min
-
-/**
- * The linear interpolation power model partially adapted from CloudSim.
- * This model is developed to adopt the <a href="http://www.spec.org/power_ssj2008/">SPECpower benchmark</a>.
- *
- * @param powerValues A [List] of average active power measured by the power analyzer(s) and accumulated by the
- * PTDaemon (Power and Temperature Daemon) for this measurement interval, displayed as watts (W).
- * @see <a href="http://www.spec.org/power_ssj2008/results/res2011q1/">Machines used in the SPEC benchmark</a>
- */
-public class InterpolationPowerModel(private val powerValues: List<Double>) : PowerModel {
- public override fun computePower(utilization: Double): Double {
- val clampedUtilization = min(1.0, max(0.0, utilization))
- val utilizationFlr = floor(clampedUtilization * 10).toInt()
- val utilizationCil = ceil(clampedUtilization * 10).toInt()
- val powerFlr: Double = getAveragePowerValue(utilizationFlr)
- val powerCil: Double = getAveragePowerValue(utilizationCil)
- val delta = (powerCil - powerFlr) / 10
-
- return if (utilization % 0.1 == 0.0) {
- getAveragePowerValue((clampedUtilization * 10).toInt())
- } else {
- powerFlr + delta * (clampedUtilization - utilizationFlr.toDouble() / 10) * 100
- }
- }
-
- override fun toString(): String = "InterpolationPowerModel[entries=${powerValues.size}]"
-
- /**
- * Gets the power consumption for a given utilization percentage.
- *
- * @param index the utilization percentage in the scale from [0 to 10],
- * where 10 means 100% of utilization.
- * @return the power consumption for the given utilization percentage
- */
- private fun getAveragePowerValue(index: Int): Double = powerValues[index]
-}
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
deleted file mode 100644
index dadc56ec..00000000
--- a/opendc-simulator/opendc-simulator-compute/src/main/kotlin/org/opendc/simulator/compute/power/LinearPowerModel.kt
+++ /dev/null
@@ -1,42 +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.power
-
-/**
- * The linear power model partially adapted from CloudSim.
- *
- * @param maxPower The maximum power draw of the server in W.
- * @param idlePower The power draw of the server at its lowest utilization level in W.
- */
-public class LinearPowerModel(private val maxPower: Double, private val idlePower: Double) : PowerModel {
- /**
- * The linear interpolation factor of the model.
- */
- private val factor: Double = (maxPower - idlePower) / 100
-
- public override fun computePower(utilization: Double): Double {
- return idlePower + factor * utilization * 100
- }
-
- override fun toString(): String = "LinearPowerModel[max=$maxPower,idle=$idlePower]"
-}
diff --git a/opendc-simulator/opendc-simulator-compute/src/main/kotlin/org/opendc/simulator/compute/power/MsePowerModel.kt b/opendc-simulator/opendc-simulator-compute/src/main/kotlin/org/opendc/simulator/compute/power/MsePowerModel.kt
deleted file mode 100644
index e9e72da8..00000000
--- a/opendc-simulator/opendc-simulator-compute/src/main/kotlin/org/opendc/simulator/compute/power/MsePowerModel.kt
+++ /dev/null
@@ -1,49 +0,0 @@
-/*
- * Copyright (c) 2021 AtLarge Research
- *
- * Permission is hereby granted, free of charge, to any person obtaining a copy
- * of this software and associated documentation files (the "Software"), to deal
- * in the Software without restriction, including without limitation the rights
- * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
- * copies of the Software, and to permit persons to whom the Software is
- * furnished to do so, subject to the following conditions:
- *
- * The above copyright notice and this permission notice shall be included in all
- * copies or substantial portions of the Software.
- *
- * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
- * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
- * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
- * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
- * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
- * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
- * SOFTWARE.
- */
-
-package org.opendc.simulator.compute.power
-
-import kotlin.math.pow
-
-/**
- * The power model that minimizes the mean squared error (MSE)
- * to the actual power measurement by tuning the calibration parameter.
- * @see <a href="https://dl.acm.org/doi/abs/10.1145/1273440.1250665">
- * Fan et al., Power provisioning for a warehouse-sized computer, ACM SIGARCH'07</a>
- *
- * @param maxPower The maximum power draw of the server in W.
- * @param idlePower The power draw of the server at its lowest utilization level in W.
- * @param calibrationParam The parameter set to minimize the MSE.
- */
-public class MsePowerModel(
- private val maxPower: Double,
- private val idlePower: Double,
- private val calibrationParam: Double
-) : PowerModel {
- private val factor: Double = (maxPower - idlePower) / 100
-
- public override fun computePower(utilization: Double): Double {
- return idlePower + factor * (2 * utilization - utilization.pow(calibrationParam)) * 100
- }
-
- override fun toString(): String = "MsePowerModel[max=$maxPower,idle=$idlePower,MSE_param=$calibrationParam]"
-}
diff --git a/opendc-simulator/opendc-simulator-compute/src/main/kotlin/org/opendc/simulator/compute/power/PStatePowerDriver.kt b/opendc-simulator/opendc-simulator-compute/src/main/kotlin/org/opendc/simulator/compute/power/PStatePowerDriver.kt
deleted file mode 100644
index ce7225d2..00000000
--- a/opendc-simulator/opendc-simulator-compute/src/main/kotlin/org/opendc/simulator/compute/power/PStatePowerDriver.kt
+++ /dev/null
@@ -1,60 +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.power
-
-import org.opendc.simulator.compute.SimMachine
-import org.opendc.simulator.compute.SimProcessingUnit
-import java.util.TreeMap
-import kotlin.math.max
-import kotlin.math.min
-
-/**
- * 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 PStatePowerDriver(states: Map<Double, PowerModel>) : PowerDriver {
- /**
- * The P-States defined by the user and ordered by key.
- */
- 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 (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 * cpus.size)
- return model.computePower(utilization)
- }
- }
-
- override fun toString(): String = "PStatePowerDriver[states=$states]"
-}
diff --git a/opendc-simulator/opendc-simulator-compute/src/main/kotlin/org/opendc/simulator/compute/power/PowerDriver.kt b/opendc-simulator/opendc-simulator-compute/src/main/kotlin/org/opendc/simulator/compute/power/PowerDriver.kt
deleted file mode 100644
index 1a46dd4a..00000000
--- a/opendc-simulator/opendc-simulator-compute/src/main/kotlin/org/opendc/simulator/compute/power/PowerDriver.kt
+++ /dev/null
@@ -1,48 +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.power
-
-import org.opendc.simulator.compute.SimMachine
-import org.opendc.simulator.compute.SimProcessingUnit
-
-/**
- * A [PowerDriver] is responsible for tracking the power usage for a component of the machine.
- */
-public interface PowerDriver {
- /**
- * Create the driver logic for the specified [machine].
- */
- public fun createLogic(machine: SimMachine, cpus: List<SimProcessingUnit>): Logic
-
- /**
- * The logic of the power driver.
- */
- public interface Logic {
- /**
- * Compute the power consumption of the component.
- *
- * @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/SimplePowerDriver.kt b/opendc-simulator/opendc-simulator-compute/src/main/kotlin/org/opendc/simulator/compute/power/SimplePowerDriver.kt
deleted file mode 100644
index 34e91c35..00000000
--- a/opendc-simulator/opendc-simulator-compute/src/main/kotlin/org/opendc/simulator/compute/power/SimplePowerDriver.kt
+++ /dev/null
@@ -1,48 +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.power
-
-import org.opendc.simulator.compute.SimMachine
-import org.opendc.simulator.compute.SimProcessingUnit
-
-/**
- * A [PowerDriver] that computes the power consumption based on a single specified [power model][model].
- */
-public class SimplePowerDriver(private val model: PowerModel) : PowerDriver {
- override fun createLogic(machine: SimMachine, cpus: List<SimProcessingUnit>): PowerDriver.Logic = object : PowerDriver.Logic {
-
- override fun computePower(): Double {
- var targetFreq = 0.0
- var totalSpeed = 0.0
-
- for (cpu in cpus) {
- targetFreq += cpu.capacity
- totalSpeed += cpu.rate
- }
-
- 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
deleted file mode 100644
index 0665dbd9..00000000
--- a/opendc-simulator/opendc-simulator-compute/src/main/kotlin/org/opendc/simulator/compute/power/SqrtPowerModel.kt
+++ /dev/null
@@ -1,41 +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.power
-
-import kotlin.math.sqrt
-
-/**
- * The square root power model partially adapted from CloudSim.
- *
- * @param maxPower The maximum power draw of the server in W.
- * @param idlePower The power draw of the server at its lowest utilization level in W.
- */
-public class SqrtPowerModel(private val maxPower: Double, private val idlePower: Double) : PowerModel {
- private val factor: Double = (maxPower - idlePower) / sqrt(100.0)
-
- override fun computePower(utilization: Double): Double {
- return idlePower + factor * sqrt(utilization * 100)
- }
-
- override fun toString(): String = "SqrtPowerModel[max=$maxPower,idle=$idlePower]"
-}
diff --git a/opendc-simulator/opendc-simulator-compute/src/main/kotlin/org/opendc/simulator/compute/power/SquarePowerModel.kt b/opendc-simulator/opendc-simulator-compute/src/main/kotlin/org/opendc/simulator/compute/power/SquarePowerModel.kt
deleted file mode 100644
index e4ae88a9..00000000
--- a/opendc-simulator/opendc-simulator-compute/src/main/kotlin/org/opendc/simulator/compute/power/SquarePowerModel.kt
+++ /dev/null
@@ -1,41 +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.power
-
-import kotlin.math.pow
-
-/**
- * The square power model partially adapted from CloudSim.
- *
- * @param maxPower The maximum power draw of the server in W.
- * @param idlePower The power draw of the server at its lowest utilization level in W.
- */
-public class SquarePowerModel(private val maxPower: Double, private val idlePower: Double) : PowerModel {
- private val factor: Double = (maxPower - idlePower) / 100.0.pow(2)
-
- override fun computePower(utilization: Double): Double {
- return idlePower + factor * (utilization * 100).pow(2)
- }
-
- override fun toString(): String = "SquarePowerModel[max=$maxPower,idle=$idlePower]"
-}
diff --git a/opendc-simulator/opendc-simulator-compute/src/main/kotlin/org/opendc/simulator/compute/power/ZeroIdlePowerDecorator.kt b/opendc-simulator/opendc-simulator-compute/src/main/kotlin/org/opendc/simulator/compute/power/ZeroIdlePowerDecorator.kt
deleted file mode 100644
index 05ab4631..00000000
--- a/opendc-simulator/opendc-simulator-compute/src/main/kotlin/org/opendc/simulator/compute/power/ZeroIdlePowerDecorator.kt
+++ /dev/null
@@ -1,40 +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.power
-
-/**
- * A decorator for ignoring the idle power when computing energy consumption of components.
- *
- * @param delegate The [PowerModel] to delegate to.
- */
-public class ZeroIdlePowerDecorator(private val delegate: PowerModel) : PowerModel {
- override fun computePower(utilization: Double): Double {
- return if (utilization == 0.0) {
- 0.0
- } else {
- delegate.computePower(utilization)
- }
- }
-
- override fun toString(): String = "ZeroIdlePowerDecorator[delegate=$delegate]"
-}
diff --git a/opendc-simulator/opendc-simulator-compute/src/main/kotlin/org/opendc/simulator/compute/workload/SimFlopsWorkload.kt b/opendc-simulator/opendc-simulator-compute/src/main/kotlin/org/opendc/simulator/compute/workload/SimFlopsWorkload.kt
deleted file mode 100644
index 726d1f56..00000000
--- a/opendc-simulator/opendc-simulator-compute/src/main/kotlin/org/opendc/simulator/compute/workload/SimFlopsWorkload.kt
+++ /dev/null
@@ -1,54 +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.workload
-
-import org.opendc.simulator.compute.SimMachineContext
-import org.opendc.simulator.flow.source.FixedFlowSource
-
-/**
- * A [SimWorkload] that models applications as a static number of floating point operations ([flops]) executed on
- * multiple cores of a compute resource.
- *
- * @property flops The number of floating point operations to perform for this task in MFLOPs.
- * @property utilization A model of the CPU utilization of the application.
- */
-public class SimFlopsWorkload(
- public val flops: Long,
- public val utilization: Double = 0.8
-) : SimWorkload {
- init {
- require(flops >= 0) { "Number of FLOPs must be positive" }
- require(utilization > 0.0 && utilization <= 1.0) { "Utilization must be in (0, 1]" }
- }
-
- override fun onStart(ctx: SimMachineContext) {
- val lifecycle = SimWorkloadLifecycle(ctx)
- for (cpu in ctx.cpus) {
- cpu.startConsumer(lifecycle.waitFor(FixedFlowSource(flops.toDouble() / ctx.cpus.size, utilization)))
- }
- }
-
- override fun onStop(ctx: SimMachineContext) {}
-
- 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
deleted file mode 100644
index 8a3f5f84..00000000
--- a/opendc-simulator/opendc-simulator-compute/src/main/kotlin/org/opendc/simulator/compute/workload/SimRuntimeWorkload.kt
+++ /dev/null
@@ -1,54 +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.workload
-
-import org.opendc.simulator.compute.SimMachineContext
-import org.opendc.simulator.flow.source.FixedFlowSource
-
-/**
- * A [SimWorkload] that models application execution as a single duration.
- *
- * @property duration The duration of the workload.
- * @property utilization The utilization of the application during runtime.
- */
-public class SimRuntimeWorkload(
- public val duration: Long,
- public val utilization: Double = 0.8
-) : SimWorkload {
- init {
- require(duration >= 0) { "Duration must be non-negative" }
- require(utilization > 0.0 && utilization <= 1.0) { "Utilization must be in (0, 1]" }
- }
-
- override fun onStart(ctx: SimMachineContext) {
- 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 onStop(ctx: SimMachineContext) {}
-
- 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
deleted file mode 100644
index db6a4629..00000000
--- a/opendc-simulator/opendc-simulator-compute/src/main/kotlin/org/opendc/simulator/compute/workload/SimTrace.kt
+++ /dev/null
@@ -1,218 +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.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 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 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(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.
- */
- @JvmStatic
- public fun ofFragments(fragments: List<SimTraceFragment>): SimTrace {
- val size = fragments.size
- val usageCol = DoubleArray(size)
- val deadlineCol = LongArray(size)
- val coresCol = IntArray(size)
-
- for (i in fragments.indices) {
- val fragment = fragments[i]
- usageCol[i] = fragment.usage
- deadlineCol[i] = fragment.timestamp + fragment.duration
- coresCol[i] = fragment.cores
- }
-
- return SimTrace(usageCol, 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 deadlineCol = LongArray(size)
- val coresCol = IntArray(size)
-
- for (i in fragments.indices) {
- val fragment = fragments[i]
- usageCol[i] = fragment.usage
- deadlineCol[i] = fragment.timestamp + fragment.duration
- coresCol[i] = fragment.cores
- }
-
- return SimTrace(usageCol, deadlineCol, coresCol, size)
- }
-
- /**
- * Create a [SimTrace.Builder] instance.
- */
- @JvmStatic
- public fun builder(): Builder = Builder()
- }
-
- /**
- * Construct a new [FlowSource] for the specified [cpu].
- *
- * @param cpu The [ProcessingUnit] for which to create the source.
- * @param offset The time offset to use for the trace.
- */
- public fun newSource(cpu: ProcessingUnit, offset: Long): FlowSource {
- return CpuConsumer(cpu, offset, usageCol, 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 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.duration, fragment.usage, fragment.cores)
- }
-
- /**
- * Add a fragment to the trace.
- *
- * @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(deadline: Long, usage: Double, cores: Int) {
- val size = size
-
- if (size == usageCol.size) {
- grow()
- }
-
- 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 + (arraySize shr 1)
-
- usageCol = usageCol.copyOf(newSize)
- deadlineCol = deadlineCol.copyOf(newSize)
- coresCol = coresCol.copyOf(newSize)
- }
-
- /**
- * Construct the immutable [SimTrace].
- */
- public fun build(): SimTrace {
- return SimTrace(usageCol, 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 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): 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 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
deleted file mode 100644
index 5285847f..00000000
--- a/opendc-simulator/opendc-simulator-compute/src/main/kotlin/org/opendc/simulator/compute/workload/SimTraceFragment.kt
+++ /dev/null
@@ -1,38 +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.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
deleted file mode 100644
index ce04a790..00000000
--- a/opendc-simulator/opendc-simulator-compute/src/main/kotlin/org/opendc/simulator/compute/workload/SimTraceWorkload.kt
+++ /dev/null
@@ -1,46 +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.workload
-
-import org.opendc.simulator.compute.SimMachineContext
-
-/**
- * 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(private val trace: SimTrace, private val offset: Long = 0L) : SimWorkload {
- override fun onStart(ctx: SimMachineContext) {
- val lifecycle = SimWorkloadLifecycle(ctx)
-
- for (cpu in ctx.cpus) {
- cpu.startConsumer(lifecycle.waitFor(trace.newSource(cpu.model, offset)))
- }
- }
-
- override fun onStop(ctx: SimMachineContext) {}
-
- override fun toString(): String = "SimTraceWorkload"
-}
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
deleted file mode 100644
index 46113bb0..00000000
--- a/opendc-simulator/opendc-simulator-compute/src/main/kotlin/org/opendc/simulator/compute/workload/SimWorkloadLifecycle.kt
+++ /dev/null
@@ -1,82 +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.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 = HashSet<Wrapper>()
-
- /**
- * Wait for the specified [source] to complete before ending the lifecycle of the workload.
- */
- public fun waitFor(source: FlowSource): FlowSource {
- val wrapper = Wrapper(source)
- waiting.add(wrapper)
- return wrapper
- }
-
- /**
- * Complete the specified [Wrapper].
- */
- private fun complete(wrapper: Wrapper) {
- if (waiting.remove(wrapper) && waiting.isEmpty()) {
- ctx.close()
- }
- }
-
- /**
- * A [FlowSource] that wraps [delegate] and informs [SimWorkloadLifecycle] that is has completed.
- */
- private inner class Wrapper(private val delegate: FlowSource) : FlowSource {
- override fun onStart(conn: FlowConnection, now: Long) {
- delegate.onStart(conn, now)
- }
-
- override fun onPull(conn: FlowConnection, now: Long): Long {
- return delegate.onPull(conn, now)
- }
-
- override fun onConverge(conn: FlowConnection, now: Long) {
- delegate.onConverge(conn, now)
- }
-
- override fun onStop(conn: FlowConnection, now: Long) {
- try {
- delegate.onStop(conn, now)
- } finally {
- complete(this)
- }
- }
-
- override fun toString(): String = "SimWorkloadLifecycle.Wrapper[delegate=$delegate]"
- }
-}
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 1eddf82c..f0aae15b 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
@@ -26,6 +26,7 @@ import kotlinx.coroutines.CancellationException
import kotlinx.coroutines.cancel
import kotlinx.coroutines.coroutineScope
import kotlinx.coroutines.launch
+import kotlinx.coroutines.yield
import org.junit.jupiter.api.Assertions.assertAll
import org.junit.jupiter.api.Assertions.assertEquals
import org.junit.jupiter.api.BeforeEach
@@ -38,17 +39,16 @@ import org.opendc.simulator.compute.model.NetworkAdapter
import org.opendc.simulator.compute.model.ProcessingNode
import org.opendc.simulator.compute.model.ProcessingUnit
import org.opendc.simulator.compute.model.StorageDevice
-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.power.CpuPowerModels
import org.opendc.simulator.compute.workload.SimFlopsWorkload
+import org.opendc.simulator.compute.workload.SimTrace
import org.opendc.simulator.compute.workload.SimWorkload
-import org.opendc.simulator.compute.workload.SimWorkloadLifecycle
-import org.opendc.simulator.flow.FlowEngine
-import org.opendc.simulator.flow.source.FixedFlowSource
+import org.opendc.simulator.flow2.FlowEngine
+import org.opendc.simulator.flow2.source.SimpleFlowSource
import org.opendc.simulator.kotlin.runSimulation
import org.opendc.simulator.network.SimNetworkSink
import org.opendc.simulator.power.SimPowerSource
+import java.util.concurrent.ThreadLocalRandom
/**
* Test suite for the [SimBareMetalMachine] class.
@@ -61,41 +61,69 @@ class SimMachineTest {
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) },
- net = listOf(NetworkAdapter("Mellanox", "ConnectX-5", 25000.0)),
- storage = listOf(StorageDevice("Samsung", "EVO", 1000.0, 250.0, 250.0))
+ /*cpus*/ List(cpuNode.coreCount) { ProcessingUnit(cpuNode, it, 1000.0) },
+ /*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() = runSimulation {
- val machine = SimBareMetalMachine(
- FlowEngine(coroutineContext, clock),
- machineModel,
- SimplePowerDriver(ConstantPowerModel(0.0))
+ val engine = FlowEngine.create(coroutineContext, clock)
+ val graph = engine.newGraph()
+
+ val machine = SimBareMetalMachine.create(
+ graph,
+ machineModel
)
- machine.runWorkload(SimFlopsWorkload(2_000, utilization = 1.0))
+ machine.runWorkload(SimFlopsWorkload(2_000, /*utilization*/ 1.0))
// Two cores execute 1000 MFlOps per second (1000 ms)
assertEquals(1000, clock.millis())
}
@Test
+ fun testTraceWorkload() = runSimulation {
+ val random = ThreadLocalRandom.current()
+ val builder = SimTrace.builder()
+ repeat(1000000) {
+ val timestamp = it.toLong() * 1000
+ val deadline = timestamp + 1000
+ builder.add(deadline, random.nextDouble(0.0, 4500.0), 1)
+ }
+ val trace = builder.build()
+
+ val engine = FlowEngine.create(coroutineContext, clock)
+ val graph = engine.newGraph()
+ val machine = SimBareMetalMachine.create(
+ graph,
+ machineModel
+ )
+
+ machine.runWorkload(trace.createWorkload(0))
+
+ // Two cores execute 1000 MFlOps per second (1000 ms)
+ assertEquals(1000000000, clock.millis())
+ }
+
+ @Test
fun testDualSocketMachine() = runSimulation {
+ val engine = FlowEngine.create(coroutineContext, clock)
+ val graph = engine.newGraph()
+
val cpuNode = machineModel.cpus[0].node
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) }
+ /*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(
- FlowEngine(coroutineContext, clock),
- machineModel,
- SimplePowerDriver(ConstantPowerModel(0.0))
+ val machine = SimBareMetalMachine.create(
+ graph,
+ machineModel
)
- machine.runWorkload(SimFlopsWorkload(2_000, utilization = 1.0))
+ machine.runWorkload(SimFlopsWorkload(2_000, /*utilization*/ 1.0))
// Two sockets with two cores execute 2000 MFlOps per second (500 ms)
assertEquals(500, clock.millis())
@@ -103,42 +131,47 @@ class SimMachineTest {
@Test
fun testPower() = runSimulation {
- val engine = FlowEngine(coroutineContext, clock)
- val machine = SimBareMetalMachine(
- engine,
+ val engine = FlowEngine.create(coroutineContext, clock)
+ val graph = engine.newGraph()
+ val machine = SimBareMetalMachine.create(
+ graph,
machineModel,
- SimplePowerDriver(LinearPowerModel(100.0, 50.0))
+ SimPsuFactories.simple(CpuPowerModels.linear(100.0, 50.0))
)
- val source = SimPowerSource(engine, capacity = 1000.0)
+ val source = SimPowerSource(graph, /*capacity*/ 1000.0f)
source.connect(machine.psu)
coroutineScope {
- launch { machine.runWorkload(SimFlopsWorkload(2_000, utilization = 1.0)) }
+ launch { machine.runWorkload(SimFlopsWorkload(2_000, /*utilization*/ 1.0)) }
+
+ yield()
assertAll(
- { assertEquals(100.0, machine.psu.powerDraw) },
- { assertEquals(100.0, source.powerDraw) }
+ { assertEquals(100.0, machine.psu.powerUsage) },
+ { assertEquals(100.0f, source.powerDraw) }
)
}
}
@Test
fun testCapacityClamp() = runSimulation {
- val machine = SimBareMetalMachine(
- FlowEngine(coroutineContext, clock),
- machineModel,
- SimplePowerDriver(ConstantPowerModel(0.0))
+ val engine = FlowEngine.create(coroutineContext, clock)
+ val graph = engine.newGraph()
+
+ val machine = SimBareMetalMachine.create(
+ graph,
+ machineModel
)
machine.runWorkload(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)
+ cpu.frequency = (cpu.model.frequency + 1000.0)
+ assertEquals(cpu.model.frequency, cpu.frequency)
+ cpu.frequency = -1.0
+ assertEquals(0.0, cpu.frequency)
- ctx.close()
+ ctx.shutdown()
}
override fun onStop(ctx: SimMachineContext) {}
@@ -147,16 +180,18 @@ class SimMachineTest {
@Test
fun testMemory() = runSimulation {
- val machine = SimBareMetalMachine(
- FlowEngine(coroutineContext, clock),
- machineModel,
- SimplePowerDriver(ConstantPowerModel(0.0))
+ val engine = FlowEngine.create(coroutineContext, clock)
+ val graph = engine.newGraph()
+
+ val machine = SimBareMetalMachine.create(
+ graph,
+ machineModel
)
machine.runWorkload(object : SimWorkload {
override fun onStart(ctx: SimMachineContext) {
assertEquals(32_000 * 4.0, ctx.memory.capacity)
- ctx.close()
+ ctx.shutdown()
}
override fun onStop(ctx: SimMachineContext) {}
@@ -165,104 +200,111 @@ class SimMachineTest {
@Test
fun testMemoryUsage() = runSimulation {
- val machine = SimBareMetalMachine(
- FlowEngine(coroutineContext, clock),
- machineModel,
- SimplePowerDriver(ConstantPowerModel(0.0))
+ val engine = FlowEngine.create(coroutineContext, clock)
+ val graph = engine.newGraph()
+
+ val machine = SimBareMetalMachine.create(
+ graph,
+ machineModel
)
machine.runWorkload(object : SimWorkload {
override fun onStart(ctx: SimMachineContext) {
- val lifecycle = SimWorkloadLifecycle(ctx)
- ctx.memory.startConsumer(lifecycle.waitFor(FixedFlowSource(ctx.memory.capacity, utilization = 0.8)))
+ val source = SimpleFlowSource(ctx.graph, ctx.memory.capacity.toFloat(), 1.0f) { ctx.shutdown() }
+ ctx.graph.connect(source.output, ctx.memory.input)
}
override fun onStop(ctx: SimMachineContext) {}
})
- assertEquals(1250, clock.millis())
+ assertEquals(1000, clock.millis())
}
@Test
fun testNetUsage() = runSimulation {
- val engine = FlowEngine(coroutineContext, clock)
- val machine = SimBareMetalMachine(
- engine,
- machineModel,
- SimplePowerDriver(ConstantPowerModel(0.0))
+ val engine = FlowEngine.create(coroutineContext, clock)
+ val graph = engine.newGraph()
+
+ val machine = SimBareMetalMachine.create(
+ graph,
+ machineModel
)
val adapter = (machine.peripherals[0] as SimNetworkAdapter)
- adapter.connect(SimNetworkSink(engine, adapter.bandwidth))
+ adapter.connect(SimNetworkSink(graph, adapter.bandwidth.toFloat()))
machine.runWorkload(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)))
+ val iface = ctx.networkInterfaces[0]
+ val source = SimpleFlowSource(ctx.graph, 800.0f, 0.8f) { ctx.shutdown(); it.close(); }
+ ctx.graph.connect(source.output, iface.tx)
}
override fun onStop(ctx: SimMachineContext) {}
})
- assertEquals(1250, clock.millis())
+ assertEquals(40, clock.millis())
}
@Test
fun testDiskReadUsage() = runSimulation {
- val engine = FlowEngine(coroutineContext, clock)
- val machine = SimBareMetalMachine(
- engine,
- machineModel,
- SimplePowerDriver(ConstantPowerModel(0.0))
+ val engine = FlowEngine.create(coroutineContext, clock)
+ val graph = engine.newGraph()
+
+ val machine = SimBareMetalMachine.create(
+ graph,
+ machineModel
)
machine.runWorkload(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)))
+ val disk = ctx.storageInterfaces[0]
+ val source = SimpleFlowSource(ctx.graph, 800.0f, 0.8f) { ctx.shutdown() }
+ ctx.graph.connect(source.output, disk.read)
}
override fun onStop(ctx: SimMachineContext) {}
})
- assertEquals(1250, clock.millis())
+ assertEquals(4000, clock.millis())
}
@Test
fun testDiskWriteUsage() = runSimulation {
- val engine = FlowEngine(coroutineContext, clock)
- val machine = SimBareMetalMachine(
- engine,
- machineModel,
- SimplePowerDriver(ConstantPowerModel(0.0))
+ val engine = FlowEngine.create(coroutineContext, clock)
+ val graph = engine.newGraph()
+
+ val machine = SimBareMetalMachine.create(
+ graph,
+ machineModel
)
machine.runWorkload(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)))
+ val disk = ctx.storageInterfaces[0]
+ val source = SimpleFlowSource(ctx.graph, 800.0f, 0.8f) { ctx.shutdown() }
+ ctx.graph.connect(source.output, disk.write)
}
override fun onStop(ctx: SimMachineContext) {}
})
- assertEquals(1250, clock.millis())
+ assertEquals(4000, clock.millis())
}
@Test
fun testCancellation() = runSimulation {
- val machine = SimBareMetalMachine(
- FlowEngine(coroutineContext, clock),
- machineModel,
- SimplePowerDriver(ConstantPowerModel(0.0))
+ val engine = FlowEngine.create(coroutineContext, clock)
+ val graph = engine.newGraph()
+
+ val machine = SimBareMetalMachine.create(
+ graph,
+ machineModel
)
try {
coroutineScope {
- launch { machine.runWorkload(SimFlopsWorkload(2_000, utilization = 1.0)) }
+ launch { machine.runWorkload(SimFlopsWorkload(2_000, /*utilization*/ 1.0)) }
cancel()
}
} catch (_: CancellationException) {
@@ -274,19 +316,21 @@ class SimMachineTest {
@Test
fun testConcurrentRuns() = runSimulation {
- val machine = SimBareMetalMachine(
- FlowEngine(coroutineContext, clock),
- machineModel,
- SimplePowerDriver(ConstantPowerModel(0.0))
+ val engine = FlowEngine.create(coroutineContext, clock)
+ val graph = engine.newGraph()
+
+ val machine = SimBareMetalMachine.create(
+ graph,
+ machineModel
)
coroutineScope {
launch {
- machine.runWorkload(SimFlopsWorkload(2_000, utilization = 1.0))
+ machine.runWorkload(SimFlopsWorkload(2_000, /*utilization*/ 1.0))
}
assertThrows<IllegalStateException> {
- machine.runWorkload(SimFlopsWorkload(2_000, utilization = 1.0))
+ machine.runWorkload(SimFlopsWorkload(2_000, /*utilization*/ 1.0))
}
}
}
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
deleted file mode 100644
index 0a6cb29f..00000000
--- a/opendc-simulator/opendc-simulator-compute/src/test/kotlin/org/opendc/simulator/compute/device/SimPsuTest.kt
+++ /dev/null
@@ -1,102 +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.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.flow.FlowEngine
-import org.opendc.simulator.kotlin.runSimulation
-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() = runSimulation {
- 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() = runSimulation {
- 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
index 6b498119..79669d40 100644
--- 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
@@ -31,20 +31,17 @@ 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.cpufreq.ScalingGovernors
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.runWorkload
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.flow.FlowEngine
-import org.opendc.simulator.flow.mux.FlowMultiplexerFactory
+import org.opendc.simulator.flow2.FlowEngine
+import org.opendc.simulator.flow2.mux.FlowMultiplexerFactory
import org.opendc.simulator.kotlin.runSimulation
import java.util.SplittableRandom
@@ -58,8 +55,8 @@ internal class SimFairShareHypervisorTest {
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) }
+ /*cpus*/ List(cpuNode.coreCount) { ProcessingUnit(cpuNode, it, 3200.0) },
+ /*memory*/ List(4) { MemoryUnit("Crucial", "MTA18ASF4G72AZ-3G2B1", 3200.0, 32_000) }
)
}
@@ -70,23 +67,20 @@ internal class SimFairShareHypervisorTest {
fun testOvercommittedSingle() = runSimulation {
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 engine = FlowEngine(coroutineContext, clock)
- val machine = SimBareMetalMachine(engine, model, SimplePowerDriver(ConstantPowerModel(0.0)))
- val hypervisor = SimHypervisor(engine, FlowMultiplexerFactory.maxMinMultiplexer(), SplittableRandom(1), PerformanceScalingGovernor())
+ 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)
+ ).createWorkload(0)
- launch {
- machine.runWorkload(hypervisor)
- println("Hypervisor finished")
- }
+ val engine = FlowEngine.create(coroutineContext, clock)
+ val graph = engine.newGraph()
+
+ val machine = SimBareMetalMachine.create(graph, model)
+ val hypervisor = SimHypervisor.create(FlowMultiplexerFactory.maxMinMultiplexer(), SplittableRandom(0L), ScalingGovernors.performance())
+
+ launch { machine.runWorkload(hypervisor) }
yield()
val vm = hypervisor.newMachine(model)
@@ -110,31 +104,27 @@ internal class SimFairShareHypervisorTest {
fun testOvercommittedDual() = runSimulation {
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)
- )
- )
+ 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)
+ ).createWorkload(0)
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 engine = FlowEngine(coroutineContext, clock)
- val machine = SimBareMetalMachine(engine, model, SimplePowerDriver(ConstantPowerModel(0.0)))
- val hypervisor = SimHypervisor(engine, FlowMultiplexerFactory.maxMinMultiplexer(), SplittableRandom(1), null)
+ 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)
+ ).createWorkload(0)
- launch {
- machine.runWorkload(hypervisor)
- }
+ val engine = FlowEngine.create(coroutineContext, clock)
+ val graph = engine.newGraph()
+
+ val machine = SimBareMetalMachine.create(graph, model)
+ val hypervisor = SimHypervisor.create(FlowMultiplexerFactory.maxMinMultiplexer(), SplittableRandom(0L), ScalingGovernors.performance())
+
+ launch { machine.runWorkload(hypervisor) }
yield()
coroutineScope {
@@ -163,18 +153,18 @@ internal class SimFairShareHypervisorTest {
fun testMultipleCPUs() = runSimulation {
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) }
+ /*cpus*/ List(cpuNode.coreCount) { ProcessingUnit(cpuNode, it, 3200.0) },
+ /*memory*/ List(4) { MemoryUnit("Crucial", "MTA18ASF4G72AZ-3G2B1", 3200.0, 32_000) }
)
- val engine = FlowEngine(coroutineContext, clock)
- val machine = SimBareMetalMachine(engine, model, SimplePowerDriver(ConstantPowerModel(0.0)))
- val hypervisor = SimHypervisor(engine, FlowMultiplexerFactory.maxMinMultiplexer(), SplittableRandom(1), null)
+ val engine = FlowEngine.create(coroutineContext, clock)
+ val graph = engine.newGraph()
+
+ val machine = SimBareMetalMachine.create(graph, model)
+ val hypervisor = SimHypervisor.create(FlowMultiplexerFactory.maxMinMultiplexer(), SplittableRandom(0L), ScalingGovernors.performance())
assertDoesNotThrow {
- launch {
- machine.runWorkload(hypervisor)
- }
+ launch { machine.runWorkload(hypervisor) }
}
machine.cancel()
@@ -184,39 +174,37 @@ internal class SimFairShareHypervisorTest {
fun testInterference() = runSimulation {
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) }
+ /*cpus*/ List(cpuNode.coreCount) { ProcessingUnit(cpuNode, it, 3200.0) },
+ /*memory*/ List(4) { MemoryUnit("Crucial", "MTA18ASF4G72AZ-3G2B1", 3200.0, 32_000) }
)
val interferenceModel = VmInterferenceModel.builder()
- .addGroup(targetLoad = 0.0, score = 0.9, members = setOf("a", "b"))
- .addGroup(targetLoad = 0.0, score = 0.6, members = setOf("a", "c"))
- .addGroup(targetLoad = 0.1, score = 0.8, members = setOf("a", "n"))
+ .addGroup(setOf("a", "b"), 0.0, 0.9)
+ .addGroup(setOf("a", "c"), 0.0, 0.6)
+ .addGroup(setOf("a", "n"), 0.1, 0.8)
.build()
- val engine = FlowEngine(coroutineContext, clock)
- val machine = SimBareMetalMachine(engine, model, SimplePowerDriver(ConstantPowerModel(0.0)))
- val hypervisor = SimHypervisor(engine, FlowMultiplexerFactory.maxMinMultiplexer(), SplittableRandom(1), null)
+ val engine = FlowEngine.create(coroutineContext, clock)
+ val graph = engine.newGraph()
+
+ val machine = SimBareMetalMachine.create(graph, model)
+ val hypervisor = SimHypervisor.create(FlowMultiplexerFactory.maxMinMultiplexer(), SplittableRandom(0L))
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)
- )
- )
+ 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)
+ ).createWorkload(0)
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)
- )
- )
+ 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)
+ ).createWorkload(0)
launch {
machine.runWorkload(hypervisor)
diff --git a/opendc-simulator/opendc-simulator-compute/src/test/kotlin/org/opendc/simulator/compute/kernel/SimSpaceSharedHypervisorTest.kt b/opendc-simulator/opendc-simulator-compute/src/test/kotlin/org/opendc/simulator/compute/kernel/SimSpaceSharedHypervisorTest.kt
index 57fe3766..ba5a5c68 100644
--- a/opendc-simulator/opendc-simulator-compute/src/test/kotlin/org/opendc/simulator/compute/kernel/SimSpaceSharedHypervisorTest.kt
+++ b/opendc-simulator/opendc-simulator-compute/src/test/kotlin/org/opendc/simulator/compute/kernel/SimSpaceSharedHypervisorTest.kt
@@ -37,16 +37,13 @@ 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.runWorkload
import org.opendc.simulator.compute.workload.SimFlopsWorkload
import org.opendc.simulator.compute.workload.SimRuntimeWorkload
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.flow.FlowEngine
-import org.opendc.simulator.flow.mux.FlowMultiplexerFactory
+import org.opendc.simulator.flow2.FlowEngine
+import org.opendc.simulator.flow2.mux.FlowMultiplexerFactory
import org.opendc.simulator.kotlin.runSimulation
import java.util.SplittableRandom
@@ -60,8 +57,8 @@ internal class SimSpaceSharedHypervisorTest {
fun setUp() {
val cpuNode = ProcessingNode("Intel", "Xeon", "amd64", 1)
machineModel = MachineModel(
- cpus = List(cpuNode.coreCount) { ProcessingUnit(cpuNode, it, 3200.0) },
- memory = List(4) { MemoryUnit("Crucial", "MTA18ASF4G72AZ-3G2B1", 3200.0, 32_000) }
+ /*cpus*/ List(cpuNode.coreCount) { ProcessingUnit(cpuNode, it, 3200.0) },
+ /*memory*/ List(4) { MemoryUnit("Crucial", "MTA18ASF4G72AZ-3G2B1", 3200.0, 32_000) }
)
}
@@ -72,18 +69,18 @@ internal class SimSpaceSharedHypervisorTest {
fun testTrace() = runSimulation {
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 engine = FlowEngine(coroutineContext, clock)
- val machine = SimBareMetalMachine(engine, machineModel, SimplePowerDriver(ConstantPowerModel(0.0)))
- val hypervisor = SimHypervisor(engine, FlowMultiplexerFactory.forwardingMultiplexer(), SplittableRandom(1), null)
+ 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)
+ ).createWorkload(0)
+
+ val engine = FlowEngine.create(coroutineContext, clock)
+ val graph = engine.newGraph()
+
+ val machine = SimBareMetalMachine.create(graph, machineModel)
+ val hypervisor = SimHypervisor.create(FlowMultiplexerFactory.forwardingMultiplexer(), SplittableRandom(0L))
launch { machine.runWorkload(hypervisor) }
val vm = hypervisor.newMachine(machineModel)
@@ -102,10 +99,12 @@ internal class SimSpaceSharedHypervisorTest {
@Test
fun testRuntimeWorkload() = runSimulation {
val duration = 5 * 60L * 1000
- val workload = SimRuntimeWorkload(duration)
- val engine = FlowEngine(coroutineContext, clock)
- val machine = SimBareMetalMachine(engine, machineModel, SimplePowerDriver(ConstantPowerModel(0.0)))
- val hypervisor = SimHypervisor(engine, FlowMultiplexerFactory.forwardingMultiplexer(), SplittableRandom(1), null)
+ val workload = SimRuntimeWorkload(duration, 1.0)
+ val engine = FlowEngine.create(coroutineContext, clock)
+ val graph = engine.newGraph()
+
+ val machine = SimBareMetalMachine.create(graph, machineModel)
+ val hypervisor = SimHypervisor.create(FlowMultiplexerFactory.forwardingMultiplexer(), SplittableRandom(0L))
launch { machine.runWorkload(hypervisor) }
yield()
@@ -125,9 +124,11 @@ internal class SimSpaceSharedHypervisorTest {
fun testFlopsWorkload() = runSimulation {
val duration = 5 * 60L * 1000
val workload = SimFlopsWorkload((duration * 3.2).toLong(), 1.0)
- val engine = FlowEngine(coroutineContext, clock)
- val machine = SimBareMetalMachine(engine, machineModel, SimplePowerDriver(ConstantPowerModel(0.0)))
- val hypervisor = SimHypervisor(engine, FlowMultiplexerFactory.forwardingMultiplexer(), SplittableRandom(1), null)
+ val engine = FlowEngine.create(coroutineContext, clock)
+ val graph = engine.newGraph()
+
+ val machine = SimBareMetalMachine.create(graph, machineModel)
+ val hypervisor = SimHypervisor.create(FlowMultiplexerFactory.forwardingMultiplexer(), SplittableRandom(0L))
launch { machine.runWorkload(hypervisor) }
yield()
@@ -144,21 +145,23 @@ internal class SimSpaceSharedHypervisorTest {
@Test
fun testTwoWorkloads() = runSimulation {
val duration = 5 * 60L * 1000
- val engine = FlowEngine(coroutineContext, clock)
- val machine = SimBareMetalMachine(engine, machineModel, SimplePowerDriver(ConstantPowerModel(0.0)))
- val hypervisor = SimHypervisor(engine, FlowMultiplexerFactory.forwardingMultiplexer(), SplittableRandom(1), null)
+ val engine = FlowEngine.create(coroutineContext, clock)
+ val graph = engine.newGraph()
+
+ val machine = SimBareMetalMachine.create(graph, machineModel)
+ val hypervisor = SimHypervisor.create(FlowMultiplexerFactory.forwardingMultiplexer(), SplittableRandom(0L))
launch { machine.runWorkload(hypervisor) }
yield()
val vm = hypervisor.newMachine(machineModel)
- vm.runWorkload(SimRuntimeWorkload(duration))
+ vm.runWorkload(SimRuntimeWorkload(duration, 1.0))
hypervisor.removeMachine(vm)
yield()
val vm2 = hypervisor.newMachine(machineModel)
- vm2.runWorkload(SimRuntimeWorkload(duration))
+ vm2.runWorkload(SimRuntimeWorkload(duration, 1.0))
hypervisor.removeMachine(vm2)
machine.cancel()
@@ -171,14 +174,18 @@ internal class SimSpaceSharedHypervisorTest {
*/
@Test
fun testConcurrentWorkloadFails() = runSimulation {
- val engine = FlowEngine(coroutineContext, clock)
- val machine = SimBareMetalMachine(engine, machineModel, SimplePowerDriver(ConstantPowerModel(0.0)))
- val hypervisor = SimHypervisor(engine, FlowMultiplexerFactory.forwardingMultiplexer(), SplittableRandom(1), null)
+ val engine = FlowEngine.create(coroutineContext, clock)
+ val graph = engine.newGraph()
+
+ val machine = SimBareMetalMachine.create(graph, machineModel)
+ val hypervisor = SimHypervisor.create(FlowMultiplexerFactory.forwardingMultiplexer(), SplittableRandom(0L))
launch { machine.runWorkload(hypervisor) }
yield()
- hypervisor.newMachine(machineModel)
+ val vm = hypervisor.newMachine(machineModel)
+ launch { vm.runWorkload(SimFlopsWorkload(10_000, 1.0)) }
+ yield()
assertAll(
{ assertFalse(hypervisor.canFit(machineModel)) },
@@ -186,6 +193,7 @@ internal class SimSpaceSharedHypervisorTest {
)
machine.cancel()
+ vm.cancel()
}
/**
@@ -193,9 +201,11 @@ internal class SimSpaceSharedHypervisorTest {
*/
@Test
fun testConcurrentWorkloadSucceeds() = runSimulation {
- val engine = FlowEngine(coroutineContext, clock)
- val machine = SimBareMetalMachine(engine, machineModel, SimplePowerDriver(ConstantPowerModel(0.0)))
- val hypervisor = SimHypervisor(engine, FlowMultiplexerFactory.forwardingMultiplexer(), SplittableRandom(1), null)
+ val engine = FlowEngine.create(coroutineContext, clock)
+ val graph = engine.newGraph()
+
+ val machine = SimBareMetalMachine.create(graph, machineModel)
+ val hypervisor = SimHypervisor.create(FlowMultiplexerFactory.forwardingMultiplexer(), SplittableRandom(0L))
launch { machine.runWorkload(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
index ef354569..6b182f4c 100644
--- 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
@@ -25,11 +25,10 @@ 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]
+ * Test suite for the conservative [ScalingGovernor].
*/
internal class ConservativeScalingGovernorTest {
@Test
@@ -38,7 +37,7 @@ internal class ConservativeScalingGovernorTest {
val minSpeed = cpuCapacity / 2
val defaultThreshold = 0.8
val defaultStepSize = 0.05 * cpuCapacity
- val governor = ConservativeScalingGovernor()
+ val governor = ScalingGovernors.conservative(defaultThreshold)
val policy = mockk<ScalingPolicy>(relaxUnitFun = true)
every { policy.max } returns cpuCapacity
@@ -48,10 +47,8 @@ internal class ConservativeScalingGovernorTest {
every { policy.target } answers { target }
every { policy.target = any() } propertyType Double::class answers { target = value }
- val logic = governor.createLogic(policy)
+ val logic = governor.newGovernor(policy)
logic.onStart()
- assertEquals(defaultThreshold, governor.threshold)
-
logic.onLimit(0.5)
/* Upwards scaling */
@@ -71,7 +68,7 @@ internal class ConservativeScalingGovernorTest {
val minSpeed = firstPState
val threshold = 0.5
val stepSize = 0.02 * cpuCapacity
- val governor = ConservativeScalingGovernor(threshold, stepSize)
+ val governor = ScalingGovernors.conservative(threshold, stepSize)
val policy = mockk<ScalingPolicy>(relaxUnitFun = true)
every { policy.max } returns cpuCapacity
@@ -81,9 +78,8 @@ internal class ConservativeScalingGovernorTest {
every { policy.target } answers { target }
every { policy.target = any() } propertyType Double::class answers { target = value }
- val logic = governor.createLogic(policy)
+ val logic = governor.newGovernor(policy)
logic.onStart()
- assertEquals(threshold, governor.threshold)
logic.onLimit(0.5)
/* Upwards scaling */
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
index ca759e39..d6a7090b 100644
--- 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
@@ -25,11 +25,10 @@ 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]
+ * Test suite for the on-demand [ScalingGovernor].
*/
internal class OnDemandScalingGovernorTest {
@Test
@@ -37,15 +36,14 @@ internal class OnDemandScalingGovernorTest {
val cpuCapacity = 4100.0
val minSpeed = cpuCapacity / 2
val defaultThreshold = 0.8
- val governor = OnDemandScalingGovernor()
+ val governor = ScalingGovernors.ondemand(defaultThreshold)
val policy = mockk<ScalingPolicy>(relaxUnitFun = true)
every { policy.min } returns minSpeed
every { policy.max } returns cpuCapacity
- val logic = governor.createLogic(policy)
+ val logic = governor.newGovernor(policy)
logic.onStart()
- assertEquals(defaultThreshold, governor.threshold)
verify(exactly = 1) { policy.target = minSpeed }
logic.onLimit(0.5)
@@ -60,16 +58,15 @@ internal class OnDemandScalingGovernorTest {
val firstPState = 1000.0
val cpuCapacity = 4100.0
val threshold = 0.5
- val governor = OnDemandScalingGovernor(threshold)
+ val governor = ScalingGovernors.ondemand(threshold)
val policy = mockk<ScalingPolicy>(relaxUnitFun = true)
every { policy.max } returns cpuCapacity
every { policy.min } returns firstPState
- val logic = governor.createLogic(policy)
+ val logic = governor.newGovernor(policy)
logic.onStart()
- assertEquals(threshold, governor.threshold)
verify(exactly = 1) { policy.target = firstPState }
logic.onLimit(0.1)
diff --git a/opendc-simulator/opendc-simulator-compute/src/test/kotlin/org/opendc/simulator/compute/kernel/cpufreq/PerformanceScalingGovernorTest.kt b/opendc-simulator/opendc-simulator-compute/src/test/kotlin/org/opendc/simulator/compute/kernel/cpufreq/PerformanceScalingGovernorTest.kt
index a4bb24f2..f03f41fe 100644
--- a/opendc-simulator/opendc-simulator-compute/src/test/kotlin/org/opendc/simulator/compute/kernel/cpufreq/PerformanceScalingGovernorTest.kt
+++ b/opendc-simulator/opendc-simulator-compute/src/test/kotlin/org/opendc/simulator/compute/kernel/cpufreq/PerformanceScalingGovernorTest.kt
@@ -34,7 +34,7 @@ internal class PerformanceScalingGovernorTest {
@Test
fun testSetStartLimit() {
val policy = spyk<ScalingPolicy>()
- val logic = PerformanceScalingGovernor().createLogic(policy)
+ val logic = ScalingGovernors.performance().newGovernor(policy)
every { policy.max } returns 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
index 662d55fb..4cee8199 100644
--- 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
@@ -36,7 +36,7 @@ internal class PowerSaveScalingGovernorTest {
val cpuCapacity = 4100.0
val minSpeed = cpuCapacity / 2
val policy = mockk<ScalingPolicy>(relaxUnitFun = true)
- val logic = PowerSaveScalingGovernor().createLogic(policy)
+ val logic = ScalingGovernors.powerSave().newGovernor(policy)
every { policy.max } returns cpuCapacity
every { policy.min } returns minSpeed
@@ -55,7 +55,7 @@ internal class PowerSaveScalingGovernorTest {
val cpuCapacity = 4100.0
val firstPState = 1000.0
val policy = mockk<ScalingPolicy>(relaxUnitFun = true)
- val logic = PowerSaveScalingGovernor().createLogic(policy)
+ val logic = ScalingGovernors.powerSave().newGovernor(policy)
every { policy.max } returns cpuCapacity
every { policy.min } returns firstPState
diff --git a/opendc-simulator/opendc-simulator-compute/src/test/kotlin/org/opendc/simulator/compute/power/PStatePowerDriverTest.kt b/opendc-simulator/opendc-simulator-compute/src/test/kotlin/org/opendc/simulator/compute/power/PStatePowerDriverTest.kt
deleted file mode 100644
index 3c0a55a6..00000000
--- a/opendc-simulator/opendc-simulator-compute/src/test/kotlin/org/opendc/simulator/compute/power/PStatePowerDriverTest.kt
+++ /dev/null
@@ -1,123 +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.power
-
-import io.mockk.every
-import io.mockk.mockk
-import org.junit.jupiter.api.Assertions.assertEquals
-import org.junit.jupiter.api.Test
-import org.opendc.simulator.compute.SimBareMetalMachine
-import org.opendc.simulator.compute.SimProcessingUnit
-
-/**
- * Test suite for [PStatePowerDriver].
- */
-internal class PStatePowerDriverTest {
- @Test
- fun testPowerBaseline() {
- val machine = mockk<SimBareMetalMachine>()
-
- val driver = PStatePowerDriver(
- sortedMapOf(
- 2800.0 to ConstantPowerModel(200.0),
- 3300.0 to ConstantPowerModel(300.0),
- 3600.0 to ConstantPowerModel(350.0)
- )
- )
-
- val logic = driver.createLogic(machine, emptyList())
- assertEquals(200.0, logic.computePower())
- }
-
- @Test
- fun testPowerWithSingleCpu() {
- val machine = mockk<SimBareMetalMachine>()
- val cpu = mockk<SimProcessingUnit>(relaxUnitFun = true)
-
- every { cpu.capacity } returns 3200.0
- every { cpu.rate } returns 1200.0
-
- val driver = PStatePowerDriver(
- sortedMapOf(
- 2800.0 to ConstantPowerModel(200.0),
- 3300.0 to ConstantPowerModel(300.0),
- 3600.0 to ConstantPowerModel(350.0)
- )
- )
-
- val logic = driver.createLogic(machine, listOf(cpu))
-
- assertEquals(300.0, logic.computePower())
- }
-
- @Test
- fun testPowerWithMultipleCpus() {
- val machine = mockk<SimBareMetalMachine>()
- 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 { cpus[1].capacity } returns 3500.0
- every { cpus[1].rate } returns 1200.0
-
- val driver = PStatePowerDriver(
- sortedMapOf(
- 2800.0 to ConstantPowerModel(200.0),
- 3300.0 to ConstantPowerModel(300.0),
- 3600.0 to ConstantPowerModel(350.0)
- )
- )
-
- val logic = driver.createLogic(machine, cpus)
-
- assertEquals(350.0, logic.computePower())
- }
-
- @Test
- fun testPowerBasedOnUtilization() {
- val machine = mockk<SimBareMetalMachine>()
- val cpu = mockk<SimProcessingUnit>(relaxUnitFun = true)
-
- every { cpu.model.frequency } returns 4200.0
-
- val driver = PStatePowerDriver(
- sortedMapOf(
- 2800.0 to LinearPowerModel(200.0, 100.0),
- 3300.0 to LinearPowerModel(250.0, 150.0),
- 4000.0 to LinearPowerModel(300.0, 200.0)
- )
- )
-
- val logic = driver.createLogic(machine, listOf(cpu))
-
- every { cpu.rate } returns 1400.0
- every { cpu.capacity } returns 1400.0
- assertEquals(150.0, logic.computePower())
-
- 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 67532d5b..9a6263c5 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
@@ -38,7 +38,7 @@ internal class PowerModelTest {
@ParameterizedTest
@MethodSource("MachinePowerModelArgs")
fun `compute power consumption given CPU loads`(
- powerModel: PowerModel,
+ powerModel: CpuPowerModel,
expectedPowerConsumption: Double
) {
val computedPowerConsumption = powerModel.computePower(cpuUtil)
@@ -48,10 +48,10 @@ internal class PowerModelTest {
@ParameterizedTest
@MethodSource("MachinePowerModelArgs")
fun `ignore idle power when computing power consumptions`(
- powerModel: PowerModel,
+ powerModel: CpuPowerModel,
expectedPowerConsumption: Double
) {
- val zeroPowerModel = ZeroIdlePowerDecorator(powerModel)
+ val zeroPowerModel = CpuPowerModels.zeroIdle(powerModel)
assertAll(
{ assertEquals(expectedPowerConsumption, zeroPowerModel.computePower(cpuUtil), epsilon) },
@@ -61,8 +61,9 @@ internal class PowerModelTest {
@Test
fun `compute power draw by the SPEC benchmark model`() {
- 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)
+ val powerModel = CpuPowerModels.interpolate(
+ 58.4, 98.0, 109.0, 118.0, 128.0, 140.0, 153.0, 170.0, 189.0, 205.0, 222.0
+ )
assertAll(
{ assertEquals(58.4, powerModel.computePower(0.0)) },
@@ -80,14 +81,14 @@ internal class PowerModelTest {
private companion object {
@JvmStatic
fun MachinePowerModelArgs(): Stream<Arguments> = Stream.of(
- Arguments.of(ConstantPowerModel(0.0), 0.0),
- Arguments.of(LinearPowerModel(350.0, 200.0), 335.0),
- Arguments.of(SquarePowerModel(350.0, 200.0), 321.5),
- Arguments.of(CubicPowerModel(350.0, 200.0), 309.35),
- Arguments.of(SqrtPowerModel(350.0, 200.0), 342.302),
- Arguments.of(MsePowerModel(350.0, 200.0, 1.4), 340.571),
- Arguments.of(AsymptoticPowerModel(350.0, 200.0, 0.3, false), 338.765),
- Arguments.of(AsymptoticPowerModel(350.0, 200.0, 0.3, true), 323.072)
+ Arguments.of(CpuPowerModels.constant(0.0), 0.0),
+ Arguments.of(CpuPowerModels.linear(350.0, 200.0), 335.0),
+ Arguments.of(CpuPowerModels.square(350.0, 200.0), 321.5),
+ Arguments.of(CpuPowerModels.cubic(350.0, 200.0), 309.35),
+ Arguments.of(CpuPowerModels.sqrt(350.0, 200.0), 342.302),
+ Arguments.of(CpuPowerModels.mse(350.0, 200.0, 1.4), 340.571),
+ Arguments.of(CpuPowerModels.asymptotic(350.0, 200.0, 0.3, false), 338.765),
+ Arguments.of(CpuPowerModels.asymptotic(350.0, 200.0, 0.3, true), 323.072)
)
}
}
diff --git a/opendc-simulator/opendc-simulator-compute/src/test/kotlin/org/opendc/simulator/compute/workload/SimFlopsWorkloadTest.kt b/opendc-simulator/opendc-simulator-compute/src/test/kotlin/org/opendc/simulator/compute/workload/SimFlopsWorkloadTest.kt
index b3e57453..edbc0571 100644
--- a/opendc-simulator/opendc-simulator-compute/src/test/kotlin/org/opendc/simulator/compute/workload/SimFlopsWorkloadTest.kt
+++ b/opendc-simulator/opendc-simulator-compute/src/test/kotlin/org/opendc/simulator/compute/workload/SimFlopsWorkloadTest.kt
@@ -32,7 +32,7 @@ class SimFlopsWorkloadTest {
@Test
fun testFlopsNonNegative() {
assertThrows<IllegalArgumentException>("FLOPs must be non-negative") {
- SimFlopsWorkload(-1)
+ SimFlopsWorkload(-1, 1.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
index 83e1f81c..e3b6e6c5 100644
--- 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
@@ -31,10 +31,8 @@ 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.runWorkload
-import org.opendc.simulator.flow.FlowEngine
+import org.opendc.simulator.flow2.FlowEngine
import org.opendc.simulator.kotlin.runSimulation
/**
@@ -48,28 +46,28 @@ class SimTraceWorkloadTest {
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) }
+ /*cpus*/ List(cpuNode.coreCount) { ProcessingUnit(cpuNode, it, 1000.0) },
+ /*memory*/ List(4) { MemoryUnit("Crucial", "MTA18ASF4G72AZ-3G2B1", 3200.0, 32_000) }
)
}
@Test
fun testSmoke() = runSimulation {
- val machine = SimBareMetalMachine(
- FlowEngine(coroutineContext, clock),
- machineModel,
- SimplePowerDriver(ConstantPowerModel(0.0))
+ val engine = FlowEngine.create(coroutineContext, clock)
+ val graph = engine.newGraph()
+
+ val machine = SimBareMetalMachine.create(
+ graph,
+ machineModel
)
- val workload = SimTraceWorkload(
+ val workload =
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
- )
+ ).createWorkload(0)
machine.runWorkload(workload)
@@ -78,21 +76,21 @@ class SimTraceWorkloadTest {
@Test
fun testOffset() = runSimulation {
- val machine = SimBareMetalMachine(
- FlowEngine(coroutineContext, clock),
- machineModel,
- SimplePowerDriver(ConstantPowerModel(0.0))
+ val engine = FlowEngine.create(coroutineContext, clock)
+ val graph = engine.newGraph()
+
+ val machine = SimBareMetalMachine.create(
+ graph,
+ machineModel
)
- val workload = SimTraceWorkload(
+ val workload =
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
- )
+ ).createWorkload(1000)
machine.runWorkload(workload)
@@ -101,21 +99,21 @@ class SimTraceWorkloadTest {
@Test
fun testSkipFragment() = runSimulation {
- val machine = SimBareMetalMachine(
- FlowEngine(coroutineContext, clock),
- machineModel,
- SimplePowerDriver(ConstantPowerModel(0.0))
+ val engine = FlowEngine.create(coroutineContext, clock)
+ val graph = engine.newGraph()
+
+ val machine = SimBareMetalMachine.create(
+ graph,
+ machineModel
)
- val workload = SimTraceWorkload(
+ val workload =
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
- )
+ ).createWorkload(0)
delay(1000L)
machine.runWorkload(workload)
@@ -125,21 +123,21 @@ class SimTraceWorkloadTest {
@Test
fun testZeroCores() = runSimulation {
- val machine = SimBareMetalMachine(
- FlowEngine(coroutineContext, clock),
- machineModel,
- SimplePowerDriver(ConstantPowerModel(0.0))
+ val engine = FlowEngine.create(coroutineContext, clock)
+ val graph = engine.newGraph()
+
+ val machine = SimBareMetalMachine.create(
+ graph,
+ machineModel
)
- val workload = SimTraceWorkload(
+ val workload =
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
- )
+ ).createWorkload(0)
machine.runWorkload(workload)
diff --git a/opendc-simulator/opendc-simulator-compute/src/test/resources/spec_machines.yml b/opendc-simulator/opendc-simulator-compute/src/test/resources/spec_machines.yml
deleted file mode 100644
index d51cba80..00000000
--- a/opendc-simulator/opendc-simulator-compute/src/test/resources/spec_machines.yml
+++ /dev/null
@@ -1,29 +0,0 @@
----
-# The power model of an IBM server x3550 (2 x [Xeon X5675 3067 MHz, 6 cores], 16GB).<br/>
-# <a href="http://www.spec.org/power_ssj2008/results/res2011q2/power_ssj2008-20110406-00368.html">
-# http://www.spec.org/power_ssj2008/results/res2011q2/power_ssj2008-20110406-00368.html</a>
-IBMx3550M3_XeonX5675: [58.4, 98.0, 109.0, 118.0, 128.0, 140.0, 153.0, 170.0, 189.0, 205.0, 222.0]
- # The power model of an IBM server x3550 (2 x [Xeon X5670 2933 MHz, 6 cores], 12GB).<br/>
- # <a href="http://www.spec.org/power_ssj2008/results/res2010q2/power_ssj2008-20100315-00239.html">
- # http://www.spec.org/power_ssj2008/results/res2010q2/power_ssj2008-20100315-00239.html</a>
-IBMx3550M3_XeonX5670: [66.0, 107.0, 120.0, 131.0, 143.0, 156.0, 173.0, 191.0, 211.0, 229.0, 247.0]
- # the power model of an IBM server x3250 (1 x [Xeon X3480 3067 MHz, 4 cores], 8GB).<br/>
- # <a href="http://www.spec.org/power_ssj2008/results/res2010q4/power_ssj2008-20101001-00297.html">
- # http://www.spec.org/power_ssj2008/results/res2010q4/power_ssj2008-20101001-00297.html</a>
-IBMx3250M3_XeonX3480: [42.3, 46.7, 49.7, 55.4, 61.8, 69.3, 76.1, 87.0, 96.1, 106.0, 113.0]
- # The power model of an IBM server x3250 (1 x [Xeon X3470 2933 MHz, 4 cores], 8GB).<br/>
- # <a href="http://www.spec.org/power_ssj2008/results/res2009q4/power_ssj2008-20091104-00213.html">
- # http://www.spec.org/power_ssj2008/results/res2009q4/power_ssj2008-20091104-00213.html</a>
-IBMx3250M3_XeonX3470: [41.6, 46.7, 52.3, 57.9, 65.4, 73.0, 80.7, 89.5, 99.6, 105.0, 113.0]
- # The power model of an HP ProLiant ML110 G5 (1 x [Xeon 3075 2660 MHz, 2 cores], 4GB).<br/>
- # <a href="http://www.spec.org/power_ssj2008/results/res2011q1/power_ssj2008-20110124-00339.html">
- # http://www.spec.org/power_ssj2008/results/res2011q1/power_ssj2008-20110124-00339.html</a>
-HPProLiantMl110G5_Xeon3075: [93.7, 97.0, 101.0, 105.0, 110.0, 116.0, 121.0, 125.0, 129.0, 133.0, 135.0]
- # The power model of an HP ProLiant ML110 G4 (1 x [Xeon 3040 1860 MHz, 2 cores], 4GB).<br/>
- # <a href="http://www.spec.org/power_ssj2008/results/res2011q1/power_ssj2008-20110127-00342.html">
- # http://www.spec.org/power_ssj2008/results/res2011q1/power_ssj2008-20110127-00342.html</a>
-HPProLiantMl110G4_Xeon3040: [86.0, 89.4, 92.6, 96.0, 99.5, 102.0, 106.0, 108.0, 112.0, 114.0, 117.0]
- # The power model of an HP ProLiant ML110 G3 (1 x [Pentium D930 3000 MHz, 2 cores], 4GB).<br/>
- # <a href="http://www.spec.org/power_ssj2008/results/res2011q1/power_ssj2008-20110127-00342.html">
- # http://www.spec.org/power_ssj2008/results/res2011q1/power_ssj2008-20110127-00342.html</a>
-HPProLiantMl110G3_PentiumD930: [105.0, 112.0, 118.0, 125.0, 131.0, 137.0, 147.0, 153.0, 157.0, 164.0, 169.0]
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
deleted file mode 100644
index 58f84d82..00000000
--- a/opendc-simulator/opendc-simulator-flow/src/jmh/kotlin/org/opendc/simulator/flow/FlowBenchmarks.kt
+++ /dev/null
@@ -1,137 +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.flow
-
-import kotlinx.coroutines.launch
-import org.opendc.simulator.flow.mux.ForwardingFlowMultiplexer
-import org.opendc.simulator.flow.mux.MaxMinFlowMultiplexer
-import org.opendc.simulator.flow.source.TraceFlowSource
-import org.opendc.simulator.kotlin.runSimulation
-import org.openjdk.jmh.annotations.Benchmark
-import org.openjdk.jmh.annotations.Fork
-import org.openjdk.jmh.annotations.Measurement
-import org.openjdk.jmh.annotations.Scope
-import org.openjdk.jmh.annotations.Setup
-import org.openjdk.jmh.annotations.State
-import org.openjdk.jmh.annotations.Warmup
-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)
-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 runSimulation {
- val engine = FlowEngine(coroutineContext, clock)
- val provider = FlowSink(engine, 4200.0)
- return@runSimulation provider.consume(TraceFlowSource(trace))
- }
- }
-
- @Benchmark
- fun benchmarkForward() {
- return runSimulation {
- val engine = FlowEngine(coroutineContext, clock)
- val provider = FlowSink(engine, 4200.0)
- val forwarder = FlowForwarder(engine)
- provider.startConsumer(forwarder)
- return@runSimulation forwarder.consume(TraceFlowSource(trace))
- }
- }
-
- @Benchmark
- fun benchmarkMuxMaxMinSingleSource() {
- return runSimulation {
- 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@runSimulation provider.consume(TraceFlowSource(trace))
- }
- }
-
- @Benchmark
- fun benchmarkMuxMaxMinTripleSource() {
- return runSimulation {
- 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 runSimulation {
- 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@runSimulation provider.consume(TraceFlowSource(trace))
- }
- }
-
- @Benchmark
- fun benchmarkMuxExclusiveTripleSource() {
- return runSimulation {
- 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/jmh/kotlin/org/opendc/simulator/flow2/FlowBenchmarks.kt b/opendc-simulator/opendc-simulator-flow/src/jmh/kotlin/org/opendc/simulator/flow2/FlowBenchmarks.kt
new file mode 100644
index 00000000..fb112082
--- /dev/null
+++ b/opendc-simulator/opendc-simulator-flow/src/jmh/kotlin/org/opendc/simulator/flow2/FlowBenchmarks.kt
@@ -0,0 +1,124 @@
+/*
+ * Copyright (c) 2021 AtLarge Research
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
+ * of this software and associated documentation files (the "Software"), to deal
+ * in the Software without restriction, including without limitation the rights
+ * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+ * copies of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included in all
+ * copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
+ * SOFTWARE.
+ */
+
+package org.opendc.simulator.flow2
+
+import kotlinx.coroutines.launch
+import org.opendc.simulator.flow2.mux.MaxMinFlowMultiplexer
+import org.opendc.simulator.flow2.sink.SimpleFlowSink
+import org.opendc.simulator.flow2.source.TraceFlowSource
+import org.opendc.simulator.flow2.util.FlowTransformer
+import org.opendc.simulator.flow2.util.FlowTransforms
+import org.opendc.simulator.kotlin.runSimulation
+import org.openjdk.jmh.annotations.Benchmark
+import org.openjdk.jmh.annotations.Fork
+import org.openjdk.jmh.annotations.Measurement
+import org.openjdk.jmh.annotations.Scope
+import org.openjdk.jmh.annotations.Setup
+import org.openjdk.jmh.annotations.State
+import org.openjdk.jmh.annotations.Warmup
+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)
+class FlowBenchmarks {
+ private lateinit var trace: TraceFlowSource.Trace
+
+ @Setup
+ fun setUp() {
+ val random = ThreadLocalRandom.current()
+ val traceSize = 10_000_000
+ trace = TraceFlowSource.Trace(
+ LongArray(traceSize) { (it + 1) * 1000L },
+ FloatArray(traceSize) { random.nextFloat(0.0f, 4500.0f) },
+ traceSize
+ )
+ }
+
+ @Benchmark
+ fun benchmarkSink() {
+ return runSimulation {
+ val engine = FlowEngine.create(coroutineContext, clock)
+ val graph = engine.newGraph()
+ val sink = SimpleFlowSink(graph, 4200.0f)
+ val source = TraceFlowSource(graph, trace)
+ graph.connect(source.output, sink.input)
+ }
+ }
+
+ @Benchmark
+ fun benchmarkForward() {
+ return runSimulation {
+ val engine = FlowEngine.create(coroutineContext, clock)
+ val graph = engine.newGraph()
+ val sink = SimpleFlowSink(graph, 4200.0f)
+ val source = TraceFlowSource(graph, trace)
+ val forwarder = FlowTransformer(graph, FlowTransforms.noop())
+
+ graph.connect(source.output, forwarder.input)
+ graph.connect(forwarder.output, sink.input)
+ }
+ }
+
+ @Benchmark
+ fun benchmarkMuxMaxMinSingleSource() {
+ return runSimulation {
+ val engine = FlowEngine.create(coroutineContext, clock)
+ val graph = engine.newGraph()
+ val switch = MaxMinFlowMultiplexer(graph)
+
+ val sinkA = SimpleFlowSink(graph, 3000.0f)
+ val sinkB = SimpleFlowSink(graph, 3000.0f)
+
+ graph.connect(switch.newOutput(), sinkA.input)
+ graph.connect(switch.newOutput(), sinkB.input)
+
+ val source = TraceFlowSource(graph, trace)
+ graph.connect(source.output, switch.newInput())
+ }
+ }
+
+ @Benchmark
+ fun benchmarkMuxMaxMinTripleSource() {
+ return runSimulation {
+ val engine = FlowEngine.create(coroutineContext, clock)
+ val graph = engine.newGraph()
+ val switch = MaxMinFlowMultiplexer(graph)
+
+ val sinkA = SimpleFlowSink(graph, 3000.0f)
+ val sinkB = SimpleFlowSink(graph, 3000.0f)
+
+ graph.connect(switch.newOutput(), sinkA.input)
+ graph.connect(switch.newOutput(), sinkB.input)
+
+ repeat(3) {
+ launch {
+ val source = TraceFlowSource(graph, trace)
+ graph.connect(source.output, switch.newInput())
+ }
+ }
+ }
+ }
+}
diff --git a/opendc-simulator/opendc-simulator-flow/src/main/java/org/opendc/simulator/flow2/FlowEngine.java b/opendc-simulator/opendc-simulator-flow/src/main/java/org/opendc/simulator/flow2/FlowEngine.java
new file mode 100644
index 00000000..0ebb0da9
--- /dev/null
+++ b/opendc-simulator/opendc-simulator-flow/src/main/java/org/opendc/simulator/flow2/FlowEngine.java
@@ -0,0 +1,260 @@
+/*
+ * Copyright (c) 2022 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.flow2;
+
+import java.time.Clock;
+import java.util.ArrayList;
+import java.util.List;
+import kotlin.coroutines.ContinuationInterceptor;
+import kotlin.coroutines.CoroutineContext;
+import kotlinx.coroutines.Delay;
+
+/**
+ * A {@link FlowEngine} simulates a generic flow network.
+ * <p>
+ * 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 final class FlowEngine implements Runnable {
+ /**
+ * The queue of {@link FlowStage} updates that are scheduled for immediate execution.
+ */
+ private final FlowStageQueue queue = new FlowStageQueue(256);
+
+ /**
+ * A priority queue containing the {@link FlowStage} updates to be scheduled in the future.
+ */
+ private final FlowTimerQueue timerQueue = new FlowTimerQueue(256);
+
+ /**
+ * The stack of engine invocations to occur in the future.
+ */
+ private final InvocationStack futureInvocations = new InvocationStack(256);
+
+ /**
+ * A flag to indicate that the engine is active.
+ */
+ private boolean active;
+
+ private final CoroutineContext coroutineContext;
+ private final Clock clock;
+ private final Delay delay;
+
+ /**
+ * Create a new {@link FlowEngine} instance using the specified {@link CoroutineContext} and {@link Clock}.
+ */
+ public static FlowEngine create(CoroutineContext coroutineContext, Clock clock) {
+ return new FlowEngine(coroutineContext, clock);
+ }
+
+ FlowEngine(CoroutineContext coroutineContext, Clock clock) {
+ this.coroutineContext = coroutineContext;
+ this.clock = clock;
+
+ CoroutineContext.Key<? extends ContinuationInterceptor> key = ContinuationInterceptor.Key;
+ this.delay = (Delay) coroutineContext.get(key);
+ }
+
+ /**
+ * Obtain the (virtual) {@link Clock} driving the simulation.
+ */
+ public Clock getClock() {
+ return clock;
+ }
+
+ /**
+ * Return a new {@link FlowGraph} that can be used to build a flow network.
+ */
+ public FlowGraph newGraph() {
+ return new RootGraph(this);
+ }
+
+ /**
+ * Enqueue the specified {@link FlowStage} to be updated immediately during the active engine cycle.
+ * <p>
+ * This method should be used when the state of a flow context is invalidated/interrupted and needs to be
+ * re-computed.
+ */
+ void scheduleImmediate(long now, FlowStage ctx) {
+ scheduleImmediateInContext(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 (active) {
+ return;
+ }
+
+ trySchedule(futureInvocations, now, now);
+ }
+
+ /**
+ * Enqueue the specified {@link FlowStage} to be updated immediately during the active engine cycle.
+ * <p>
+ * This method should be used when the state of a flow context is invalidated/interrupted and needs to be
+ * re-computed.
+ * <p>
+ * This method should only be invoked while inside an engine cycle.
+ */
+ void scheduleImmediateInContext(FlowStage ctx) {
+ queue.add(ctx);
+ }
+
+ /**
+ * Enqueue the specified {@link FlowStage} to be updated at its updated deadline.
+ */
+ void scheduleDelayed(FlowStage ctx) {
+ scheduleDelayedInContext(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 (active) {
+ return;
+ }
+
+ long deadline = timerQueue.peekDeadline();
+ if (deadline != Long.MAX_VALUE) {
+ trySchedule(futureInvocations, clock.millis(), deadline);
+ }
+ }
+
+ /**
+ * Enqueue the specified {@link FlowStage} to be updated at its updated deadline.
+ * <p>
+ * This method should only be invoked while inside an engine cycle.
+ */
+ void scheduleDelayedInContext(FlowStage ctx) {
+ FlowTimerQueue timerQueue = this.timerQueue;
+ timerQueue.enqueue(ctx);
+ }
+
+ /**
+ * Run all the enqueued actions for the specified timestamp (<code>now</code>).
+ */
+ private void doRunEngine(long now) {
+ final FlowStageQueue queue = this.queue;
+ final FlowTimerQueue timerQueue = this.timerQueue;
+
+ try {
+ // Mark the engine as active to prevent concurrent calls to this method
+ active = true;
+
+ // Execute all scheduled updates at current timestamp
+ while (true) {
+ final FlowStage ctx = timerQueue.poll(now);
+ if (ctx == null) {
+ break;
+ }
+
+ ctx.onUpdate(now);
+ }
+
+ // Execute all immediate updates
+ while (true) {
+ final FlowStage ctx = queue.poll();
+ if (ctx == null) {
+ break;
+ }
+
+ ctx.onUpdate(now);
+ }
+ } finally {
+ active = false;
+ }
+
+ // Schedule an engine invocation for the next update to occur.
+ long headDeadline = timerQueue.peekDeadline();
+ if (headDeadline != Long.MAX_VALUE && headDeadline >= now) {
+ trySchedule(futureInvocations, now, headDeadline);
+ }
+ }
+
+ @Override
+ public void run() {
+ doRunEngine(futureInvocations.poll());
+ }
+
+ /**
+ * Try to schedule an engine invocation at the specified [target].
+ *
+ * @param scheduled The queue of scheduled invocations.
+ * @param now The current virtual timestamp.
+ * @param target The virtual timestamp at which the engine invocation should happen.
+ */
+ private void trySchedule(InvocationStack scheduled, long now, long target) {
+ // Only schedule a new scheduler invocation in case the target is earlier than all other pending
+ // scheduler invocations
+ if (scheduled.tryAdd(target)) {
+ delay.invokeOnTimeout(target - now, this, coroutineContext);
+ }
+ }
+
+ /**
+ * Internal implementation of a root {@link FlowGraph}.
+ */
+ private static final class RootGraph implements FlowGraphInternal {
+ private final FlowEngine engine;
+ private final List<FlowStage> stages = new ArrayList<>();
+
+ public RootGraph(FlowEngine engine) {
+ this.engine = engine;
+ }
+
+ @Override
+ public FlowEngine getEngine() {
+ return engine;
+ }
+
+ @Override
+ public FlowStage newStage(FlowStageLogic logic) {
+ final FlowEngine engine = this.engine;
+ final FlowStage stage = new FlowStage(this, logic);
+ stages.add(stage);
+ long now = engine.getClock().millis();
+ stage.invalidate(now);
+ return stage;
+ }
+
+ @Override
+ public void connect(Outlet outlet, Inlet inlet) {
+ FlowGraphInternal.connect(this, outlet, inlet);
+ }
+
+ @Override
+ public void disconnect(Outlet outlet) {
+ FlowGraphInternal.disconnect(this, outlet);
+ }
+
+ @Override
+ public void disconnect(Inlet inlet) {
+ FlowGraphInternal.disconnect(this, inlet);
+ }
+
+ /**
+ * Internal method to remove the specified {@link FlowStage} from the graph.
+ */
+ @Override
+ public void detach(FlowStage stage) {
+ stages.remove(stage);
+ }
+ }
+}
diff --git a/opendc-simulator/opendc-simulator-flow/src/main/java/org/opendc/simulator/flow2/FlowGraph.java b/opendc-simulator/opendc-simulator-flow/src/main/java/org/opendc/simulator/flow2/FlowGraph.java
new file mode 100644
index 00000000..f45be6cd
--- /dev/null
+++ b/opendc-simulator/opendc-simulator-flow/src/main/java/org/opendc/simulator/flow2/FlowGraph.java
@@ -0,0 +1,63 @@
+/*
+ * Copyright (c) 2022 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.flow2;
+
+/**
+ * A representation of a flow network. A flow network is a directed graph where each edge has a capacity and receives an
+ * amount of flow that cannot exceed the edge's capacity.
+ */
+public interface FlowGraph {
+ /**
+ * Return the {@link FlowEngine} driving the simulation of the graph.
+ */
+ FlowEngine getEngine();
+
+ /**
+ * Create a new {@link FlowStage} representing a node in the flow network.
+ *
+ * @param logic The logic for handling the events of the stage.
+ */
+ FlowStage newStage(FlowStageLogic logic);
+
+ /**
+ * Add an edge between the specified outlet port and inlet port in this graph.
+ *
+ * @param outlet The outlet of the source from which the flow originates.
+ * @param inlet The inlet of the sink that should receive the flow.
+ */
+ void connect(Outlet outlet, Inlet inlet);
+
+ /**
+ * Disconnect the specified {@link Outlet} (if connected).
+ *
+ * @param outlet The outlet to disconnect.
+ */
+ void disconnect(Outlet outlet);
+
+ /**
+ * Disconnect the specified {@link Inlet} (if connected).
+ *
+ * @param inlet The inlet to disconnect.
+ */
+ void disconnect(Inlet inlet);
+}
diff --git a/opendc-simulator/opendc-simulator-flow/src/main/java/org/opendc/simulator/flow2/FlowGraphInternal.java b/opendc-simulator/opendc-simulator-flow/src/main/java/org/opendc/simulator/flow2/FlowGraphInternal.java
new file mode 100644
index 00000000..0f608b60
--- /dev/null
+++ b/opendc-simulator/opendc-simulator-flow/src/main/java/org/opendc/simulator/flow2/FlowGraphInternal.java
@@ -0,0 +1,93 @@
+/*
+ * Copyright (c) 2022 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.flow2;
+
+/**
+ * Interface implemented by {@link FlowGraph} implementations.
+ */
+interface FlowGraphInternal extends FlowGraph {
+ /**
+ * Internal method to remove the specified {@link FlowStage} from the graph.
+ */
+ void detach(FlowStage stage);
+
+ /**
+ * Helper method to connect an outlet to an inlet.
+ */
+ static void connect(FlowGraph graph, Outlet outlet, Inlet inlet) {
+ if (!(outlet instanceof OutPort) || !(inlet instanceof InPort)) {
+ throw new IllegalArgumentException("Invalid outlet or inlet passed to graph");
+ }
+
+ InPort inPort = (InPort) inlet;
+ OutPort outPort = (OutPort) outlet;
+
+ if (!graph.equals(outPort.getGraph()) || !graph.equals(inPort.getGraph())) {
+ throw new IllegalArgumentException("Outlet or inlet does not belong to graph");
+ } else if (outPort.input != null || inPort.output != null) {
+ throw new IllegalStateException("Inlet or outlet already connected");
+ }
+
+ outPort.input = inPort;
+ inPort.output = outPort;
+
+ inPort.connect();
+ outPort.connect();
+ }
+
+ /**
+ * Helper method to disconnect an outlet.
+ */
+ static void disconnect(FlowGraph graph, Outlet outlet) {
+ if (!(outlet instanceof OutPort)) {
+ throw new IllegalArgumentException("Invalid outlet passed to graph");
+ }
+
+ OutPort outPort = (OutPort) outlet;
+
+ if (!graph.equals(outPort.getGraph())) {
+ throw new IllegalArgumentException("Outlet or inlet does not belong to graph");
+ }
+
+ outPort.cancel(null);
+ outPort.complete();
+ }
+
+ /**
+ * Helper method to disconnect an inlet.
+ */
+ static void disconnect(FlowGraph graph, Inlet inlet) {
+ if (!(inlet instanceof InPort)) {
+ throw new IllegalArgumentException("Invalid outlet passed to graph");
+ }
+
+ InPort inPort = (InPort) inlet;
+
+ if (!graph.equals(inPort.getGraph())) {
+ throw new IllegalArgumentException("Outlet or inlet does not belong to graph");
+ }
+
+ inPort.finish(null);
+ inPort.cancel(null);
+ }
+}
diff --git a/opendc-simulator/opendc-simulator-flow/src/main/java/org/opendc/simulator/flow2/FlowStage.java b/opendc-simulator/opendc-simulator-flow/src/main/java/org/opendc/simulator/flow2/FlowStage.java
new file mode 100644
index 00000000..4d098043
--- /dev/null
+++ b/opendc-simulator/opendc-simulator-flow/src/main/java/org/opendc/simulator/flow2/FlowStage.java
@@ -0,0 +1,303 @@
+/*
+ * Copyright (c) 2022 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.flow2;
+
+import java.time.Clock;
+import java.util.HashMap;
+import java.util.Map;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+/**
+ * A {@link FlowStage} represents a node in a {@link FlowGraph}.
+ */
+public final class FlowStage {
+ private static final Logger LOGGER = LoggerFactory.getLogger(FlowStage.class);
+
+ /**
+ * States of the flow stage.
+ */
+ private static final int STAGE_PENDING = 0; // Stage is pending to be started
+
+ private static final int STAGE_ACTIVE = 1; // Stage is actively running
+ private static final int STAGE_CLOSED = 2; // Stage is closed
+ private static final int STAGE_STATE = 0b11; // Mask for accessing the state of the flow stage
+
+ /**
+ * Flags of the flow connection
+ */
+ private static final int STAGE_INVALIDATE = 1 << 2; // The stage is invalidated
+
+ private static final int STAGE_CLOSE = 1 << 3; // The stage should be closed
+ private static final int STAGE_UPDATE_ACTIVE = 1 << 4; // An update for the connection is active
+ private static final int STAGE_UPDATE_PENDING = 1 << 5; // An (immediate) update of the connection is pending
+
+ /**
+ * The flags representing the state and pending actions for the stage.
+ */
+ private int flags = STAGE_PENDING;
+
+ /**
+ * The deadline of the stage after which an update should run.
+ */
+ long deadline = Long.MAX_VALUE;
+
+ /**
+ * The index of the timer in the {@link FlowTimerQueue}.
+ */
+ int timerIndex = -1;
+
+ final Clock clock;
+ private final FlowStageLogic logic;
+ final FlowGraphInternal parentGraph;
+ private final FlowEngine engine;
+
+ private final Map<String, InPort> inlets = new HashMap<>();
+ private final Map<String, OutPort> outlets = new HashMap<>();
+ private int nextInlet = 0;
+ private int nextOutlet = 0;
+
+ /**
+ * Construct a new {@link FlowStage} instance.
+ *
+ * @param parentGraph The {@link FlowGraph} this stage belongs to.
+ * @param logic The logic of the stage.
+ */
+ FlowStage(FlowGraphInternal parentGraph, FlowStageLogic logic) {
+ this.parentGraph = parentGraph;
+ this.logic = logic;
+ this.engine = parentGraph.getEngine();
+ this.clock = engine.getClock();
+ }
+
+ /**
+ * Return the {@link FlowGraph} to which this stage belongs.
+ */
+ public FlowGraph getGraph() {
+ return parentGraph;
+ }
+
+ /**
+ * Return the {@link Inlet} (an in-going edge) with the specified <code>name</code> for this {@link FlowStage}.
+ * If an inlet with that name does not exist, a new one is allocated for the stage.
+ *
+ * @param name The name of the inlet.
+ * @return The {@link InPort} representing an {@link Inlet} with the specified <code>name</code>.
+ */
+ public InPort getInlet(String name) {
+ return inlets.computeIfAbsent(name, (key) -> new InPort(this, key, nextInlet++));
+ }
+
+ /**
+ * Return the {@link Outlet} (an out-going edge) with the specified <code>name</code> for this {@link FlowStage}.
+ * If an outlet with that name does not exist, a new one is allocated for the stage.
+ *
+ * @param name The name of the outlet.
+ * @return The {@link OutPort} representing an {@link Outlet} with the specified <code>name</code>.
+ */
+ public OutPort getOutlet(String name) {
+ return outlets.computeIfAbsent(name, (key) -> new OutPort(this, key, nextOutlet++));
+ }
+
+ /**
+ * Return the current deadline of the {@link FlowStage}'s timer (in milliseconds after epoch).
+ */
+ public long getDeadline() {
+ return deadline;
+ }
+
+ /**
+ * Set the deadline of the {@link FlowStage}'s timer.
+ *
+ * @param deadline The new deadline (in milliseconds after epoch) when the stage should be interrupted.
+ */
+ public void setDeadline(long deadline) {
+ this.deadline = deadline;
+
+ if ((flags & STAGE_UPDATE_ACTIVE) == 0) {
+ // Update the timer queue with the new deadline
+ engine.scheduleDelayed(this);
+ }
+ }
+
+ /**
+ * Invalidate the {@link FlowStage} forcing the stage to update.
+ */
+ public void invalidate() {
+ int flags = this.flags;
+
+ if ((flags & STAGE_UPDATE_ACTIVE) == 0) {
+ scheduleImmediate(clock.millis(), flags | STAGE_INVALIDATE);
+ }
+ }
+
+ /**
+ * Close the {@link FlowStage} and disconnect all inlets and outlets.
+ */
+ public void close() {
+ int flags = this.flags;
+
+ if ((flags & STAGE_STATE) == STAGE_CLOSED) {
+ return;
+ }
+
+ // Toggle the close bit. In case no update is active, schedule a new update.
+ if ((flags & STAGE_UPDATE_ACTIVE) != 0) {
+ this.flags = flags | STAGE_CLOSE;
+ } else {
+ scheduleImmediate(clock.millis(), flags | STAGE_CLOSE);
+ }
+ }
+
+ /**
+ * Update the state of the flow stage.
+ *
+ * @param now The current virtual timestamp.
+ */
+ void onUpdate(long now) {
+ int flags = this.flags;
+ int state = flags & STAGE_STATE;
+
+ if (state == STAGE_ACTIVE) {
+ doUpdate(now, flags);
+ } else if (state == STAGE_PENDING) {
+ doStart(now, flags);
+ }
+ }
+
+ /**
+ * Invalidate the {@link FlowStage} forcing the stage to update.
+ *
+ * <p>
+ * This method is similar to {@link #invalidate()}, but allows the user to manually pass the current timestamp to
+ * prevent having to re-query the clock. This method should not be called during an update.
+ */
+ void invalidate(long now) {
+ scheduleImmediate(now, flags | STAGE_INVALIDATE);
+ }
+
+ /**
+ * Schedule an immediate update for this stage.
+ */
+ private void scheduleImmediate(long now, int flags) {
+ // In case an immediate update is already scheduled, no need to do anything
+ if ((flags & STAGE_UPDATE_PENDING) != 0) {
+ this.flags = flags;
+ return;
+ }
+
+ // Mark the stage that there is an update pending
+ this.flags = flags | STAGE_UPDATE_PENDING;
+
+ engine.scheduleImmediate(now, this);
+ }
+
+ /**
+ * Start the stage.
+ */
+ private void doStart(long now, int flags) {
+ // Update state before calling into the outside world, so it observes a consistent state
+ flags = flags | STAGE_ACTIVE | STAGE_UPDATE_ACTIVE;
+
+ doUpdate(now, flags);
+ }
+
+ /**
+ * Update the state of the stage.
+ */
+ private void doUpdate(long now, int flags) {
+ long deadline = this.deadline;
+ long newDeadline = deadline;
+
+ // Update the stage if:
+ // (1) the timer of the stage has expired.
+ // (2) one of the input ports is pushed,
+ // (3) one of the output ports is pulled,
+ if ((flags & STAGE_INVALIDATE) != 0 || deadline == now) {
+ // Update state before calling into the outside world, so it observes a consistent state
+ this.flags = (flags & ~STAGE_INVALIDATE) | STAGE_UPDATE_ACTIVE;
+
+ try {
+ newDeadline = logic.onUpdate(this, now);
+
+ // IMPORTANT: Re-fetch the flags after the callback might have changed those
+ flags = this.flags;
+ } catch (Exception e) {
+ doFail(e);
+ }
+ }
+
+ // Check whether the stage is marked as closing.
+ if ((flags & STAGE_CLOSE) != 0) {
+ doClose(flags, null);
+
+ // IMPORTANT: Re-fetch the flags after the callback might have changed those
+ flags = this.flags;
+ }
+
+ // Indicate that no update is active anymore and flush the flags
+ this.flags = flags & ~(STAGE_UPDATE_ACTIVE | STAGE_UPDATE_PENDING);
+ this.deadline = newDeadline;
+
+ // Update the timer queue with the new deadline
+ engine.scheduleDelayedInContext(this);
+ }
+
+ /**
+ * This method is invoked when an uncaught exception is caught by the engine. When this happens, the
+ * {@link FlowStageLogic} "fails" and disconnects all its inputs and outputs.
+ */
+ void doFail(Throwable cause) {
+ LOGGER.warn("Uncaught exception (closing stage)", cause);
+
+ doClose(flags, cause);
+ }
+
+ /**
+ * This method is invoked when the {@link FlowStageLogic} exits successfully or due to failure.
+ */
+ private void doClose(int flags, Throwable cause) {
+ // Mark the stage as closed
+ this.flags = flags & ~(STAGE_STATE | STAGE_INVALIDATE | STAGE_CLOSE) | STAGE_CLOSED;
+
+ // Remove stage from parent graph
+ parentGraph.detach(this);
+
+ // Remove stage from the timer queue
+ setDeadline(Long.MAX_VALUE);
+
+ // Cancel all input ports
+ for (InPort port : inlets.values()) {
+ if (port != null) {
+ port.cancel(cause);
+ }
+ }
+
+ // Cancel all output ports
+ for (OutPort port : outlets.values()) {
+ if (port != null) {
+ port.fail(cause);
+ }
+ }
+ }
+}
diff --git a/opendc-simulator/opendc-simulator-flow/src/main/java/org/opendc/simulator/flow2/FlowStageLogic.java b/opendc-simulator/opendc-simulator-flow/src/main/java/org/opendc/simulator/flow2/FlowStageLogic.java
new file mode 100644
index 00000000..70986a35
--- /dev/null
+++ b/opendc-simulator/opendc-simulator-flow/src/main/java/org/opendc/simulator/flow2/FlowStageLogic.java
@@ -0,0 +1,38 @@
+/*
+ * Copyright (c) 2022 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.flow2;
+
+/**
+ * The {@link FlowStageLogic} interface is responsible for describing the behaviour of a {@link FlowStage} via
+ * out-going flows based on its potential inputs.
+ */
+public interface FlowStageLogic {
+ /**
+ * This method is invoked when the one of the stage's inlets or outlets is invalidated.
+ *
+ * @param ctx The context in which the stage runs.
+ * @param now The virtual timestamp in milliseconds after epoch at which the update is occurring.
+ * @return The next deadline for the stage.
+ */
+ long onUpdate(FlowStage ctx, long now);
+}
diff --git a/opendc-simulator/opendc-simulator-flow/src/main/java/org/opendc/simulator/flow2/FlowStageQueue.java b/opendc-simulator/opendc-simulator-flow/src/main/java/org/opendc/simulator/flow2/FlowStageQueue.java
new file mode 100644
index 00000000..56ec7702
--- /dev/null
+++ b/opendc-simulator/opendc-simulator-flow/src/main/java/org/opendc/simulator/flow2/FlowStageQueue.java
@@ -0,0 +1,109 @@
+/*
+ * Copyright (c) 2022 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.flow2;
+
+import java.util.ArrayDeque;
+import java.util.Arrays;
+
+/**
+ * A specialized {@link ArrayDeque} implementation that contains the {@link FlowStageLogic}s
+ * that have been updated during the engine cycle and should converge.
+ * <p>
+ * By using a specialized class, we reduce the overhead caused by type-erasure.
+ */
+final class FlowStageQueue {
+ /**
+ * The array of elements in the queue.
+ */
+ private FlowStage[] elements;
+
+ private int head = 0;
+ private int tail = 0;
+
+ public FlowStageQueue(int initialCapacity) {
+ elements = new FlowStage[initialCapacity];
+ }
+
+ /**
+ * Add the specified context to the queue.
+ */
+ void add(FlowStage ctx) {
+ final FlowStage[] es = elements;
+ int tail = this.tail;
+
+ es[tail] = ctx;
+
+ tail = inc(tail, es.length);
+ this.tail = tail;
+
+ if (head == tail) {
+ doubleCapacity();
+ }
+ }
+
+ /**
+ * Remove a {@link FlowStage} from the queue or <code>null</code> if the queue is empty.
+ */
+ FlowStage poll() {
+ final FlowStage[] es = elements;
+ int head = this.head;
+ FlowStage ctx = es[head];
+
+ if (ctx != null) {
+ es[head] = null;
+ this.head = inc(head, es.length);
+ }
+
+ return ctx;
+ }
+
+ /**
+ * Doubles the capacity of this deque
+ */
+ private void doubleCapacity() {
+ int oldCapacity = elements.length;
+ int newCapacity = oldCapacity + (oldCapacity >> 1);
+ if (newCapacity < 0) {
+ throw new IllegalStateException("Sorry, deque too big");
+ }
+
+ final FlowStage[] es = elements = Arrays.copyOf(elements, newCapacity);
+
+ // Exceptionally, here tail == head needs to be disambiguated
+ if (tail < head || (tail == head && es[head] != null)) {
+ // wrap around; slide first leg forward to end of array
+ int newSpace = newCapacity - oldCapacity;
+ System.arraycopy(es, head, es, head + newSpace, oldCapacity - head);
+ for (int i = head, to = (head += newSpace); i < to; i++) es[i] = null;
+ }
+ }
+
+ /**
+ * Circularly increments i, mod modulus.
+ * Precondition and postcondition: 0 <= i < modulus.
+ */
+ private static int inc(int i, int modulus) {
+ if (++i >= modulus) i = 0;
+ return i;
+ }
+}
diff --git a/opendc-simulator/opendc-simulator-flow/src/main/java/org/opendc/simulator/flow2/FlowTimerQueue.java b/opendc-simulator/opendc-simulator-flow/src/main/java/org/opendc/simulator/flow2/FlowTimerQueue.java
new file mode 100644
index 00000000..4b746202
--- /dev/null
+++ b/opendc-simulator/opendc-simulator-flow/src/main/java/org/opendc/simulator/flow2/FlowTimerQueue.java
@@ -0,0 +1,208 @@
+/*
+ * Copyright (c) 2022 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.flow2;
+
+import java.util.Arrays;
+
+/**
+ * A specialized priority queue for timers of {@link FlowStageLogic}s.
+ * <p>
+ * By using a specialized priority queue, we reduce the overhead caused by the default priority queue implementation
+ * being generic.
+ */
+final class FlowTimerQueue {
+ /**
+ * Array representation of binary heap of {@link FlowStage} instances.
+ */
+ private FlowStage[] queue;
+
+ /**
+ * The number of elements in the priority queue.
+ */
+ private int size = 0;
+
+ /**
+ * Construct a {@link FlowTimerQueue} with the specified initial capacity.
+ *
+ * @param initialCapacity The initial capacity of the queue.
+ */
+ public FlowTimerQueue(int initialCapacity) {
+ this.queue = new FlowStage[initialCapacity];
+ }
+
+ /**
+ * Enqueue a timer for the specified context or update the existing timer.
+ */
+ void enqueue(FlowStage ctx) {
+ FlowStage[] es = queue;
+ int k = ctx.timerIndex;
+
+ if (ctx.deadline != Long.MAX_VALUE) {
+ if (k >= 0) {
+ update(es, ctx, k);
+ } else {
+ add(es, ctx);
+ }
+ } else if (k >= 0) {
+ delete(es, k);
+ }
+ }
+
+ /**
+ * Retrieve the head of the queue if its deadline does not exceed <code>now</code>.
+ *
+ * @param now The timestamp that the deadline of the head of the queue should not exceed.
+ * @return The head of the queue if its deadline does not exceed <code>now</code>, otherwise <code>null</code>.
+ */
+ FlowStage poll(long now) {
+ int size = this.size;
+ if (size == 0) {
+ return null;
+ }
+
+ final FlowStage[] es = queue;
+ final FlowStage head = es[0];
+
+ if (now < head.deadline) {
+ return null;
+ }
+
+ int n = size - 1;
+ this.size = n;
+ final FlowStage next = es[n];
+ es[n] = null; // Clear the last element of the queue
+
+ if (n > 0) {
+ siftDown(0, next, es, n);
+ }
+
+ head.timerIndex = -1;
+ return head;
+ }
+
+ /**
+ * Find the earliest deadline in the queue.
+ */
+ long peekDeadline() {
+ if (size > 0) {
+ return queue[0].deadline;
+ }
+
+ return Long.MAX_VALUE;
+ }
+
+ /**
+ * Add a new entry to the queue.
+ */
+ private void add(FlowStage[] es, FlowStage ctx) {
+ int i = size;
+
+ if (i >= es.length) {
+ // Re-fetch the resized array
+ es = grow();
+ }
+
+ siftUp(i, ctx, es);
+
+ size = i + 1;
+ }
+
+ /**
+ * Update the deadline of an existing entry in the queue.
+ */
+ private void update(FlowStage[] es, FlowStage ctx, int k) {
+ if (k > 0) {
+ int parent = (k - 1) >>> 1;
+ if (es[parent].deadline > ctx.deadline) {
+ siftUp(k, ctx, es);
+ return;
+ }
+ }
+
+ siftDown(k, ctx, es, size);
+ }
+
+ /**
+ * Deadline an entry from the queue.
+ */
+ private void delete(FlowStage[] es, int k) {
+ int s = --size;
+ if (s == k) {
+ es[k] = null; // Element is last in the queue
+ } else {
+ FlowStage moved = es[s];
+ es[s] = null;
+
+ siftDown(k, moved, es, s);
+
+ if (es[k] == moved) {
+ siftUp(k, moved, es);
+ }
+ }
+ }
+
+ /**
+ * Increases the capacity of the array.
+ */
+ private FlowStage[] grow() {
+ FlowStage[] queue = this.queue;
+ int oldCapacity = queue.length;
+ int newCapacity = oldCapacity + (oldCapacity >> 1);
+
+ queue = Arrays.copyOf(queue, newCapacity);
+ this.queue = queue;
+ return queue;
+ }
+
+ private static void siftUp(int k, FlowStage key, FlowStage[] es) {
+ while (k > 0) {
+ int parent = (k - 1) >>> 1;
+ FlowStage e = es[parent];
+ if (key.deadline >= e.deadline) break;
+ es[k] = e;
+ e.timerIndex = k;
+ k = parent;
+ }
+ es[k] = key;
+ key.timerIndex = k;
+ }
+
+ private static void siftDown(int k, FlowStage key, FlowStage[] es, int n) {
+ int half = n >>> 1; // loop while a non-leaf
+ while (k < half) {
+ int child = (k << 1) + 1; // assume left child is least
+ FlowStage c = es[child];
+ int right = child + 1;
+ if (right < n && c.deadline > es[right].deadline) c = es[child = right];
+
+ if (key.deadline <= c.deadline) break;
+
+ es[k] = c;
+ c.timerIndex = k;
+ k = child;
+ }
+
+ es[k] = key;
+ key.timerIndex = k;
+ }
+}
diff --git a/opendc-simulator/opendc-simulator-flow/src/main/java/org/opendc/simulator/flow2/InHandler.java b/opendc-simulator/opendc-simulator-flow/src/main/java/org/opendc/simulator/flow2/InHandler.java
new file mode 100644
index 00000000..839b01db
--- /dev/null
+++ b/opendc-simulator/opendc-simulator-flow/src/main/java/org/opendc/simulator/flow2/InHandler.java
@@ -0,0 +1,54 @@
+/*
+ * Copyright (c) 2022 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.flow2;
+
+/**
+ * Collection of callbacks for the input port (a {@link InPort}) of a {@link FlowStageLogic}.
+ */
+public interface InHandler {
+ /**
+ * Return the actual flow rate over the input port.
+ *
+ * @param port The input port to which the flow was pushed.
+ * @return The actual flow rate over the port.
+ */
+ default float getRate(InPort port) {
+ return Math.min(port.getDemand(), port.getCapacity());
+ }
+
+ /**
+ * This method is invoked when another {@link FlowStageLogic} changes the rate of flow to the specified inlet.
+ *
+ * @param port The input port to which the flow was pushed.
+ * @param demand The rate of flow the output attempted to push to the port.
+ */
+ void onPush(InPort port, float demand);
+
+ /**
+ * This method is invoked when the input port is finished.
+ *
+ * @param port The input port that has finished.
+ * @param cause The cause of the input port being finished or <code>null</code> if the port completed successfully.
+ */
+ void onUpstreamFinish(InPort port, Throwable cause);
+}
diff --git a/opendc-simulator/opendc-simulator-flow/src/main/java/org/opendc/simulator/flow2/InHandlers.java b/opendc-simulator/opendc-simulator-flow/src/main/java/org/opendc/simulator/flow2/InHandlers.java
new file mode 100644
index 00000000..9d5b4bef
--- /dev/null
+++ b/opendc-simulator/opendc-simulator-flow/src/main/java/org/opendc/simulator/flow2/InHandlers.java
@@ -0,0 +1,53 @@
+/*
+ * Copyright (c) 2022 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.flow2;
+
+/**
+ * A collection of common {@link InHandler} implementations.
+ */
+public class InHandlers {
+ /**
+ * Prevent construction of this class.
+ */
+ private InHandlers() {}
+
+ /**
+ * Return an {@link InHandler} that does nothing.
+ */
+ public static InHandler noop() {
+ return NoopInHandler.INSTANCE;
+ }
+
+ /**
+ * No-op implementation of {@link InHandler}.
+ */
+ private static final class NoopInHandler implements InHandler {
+ public static final InHandler INSTANCE = new NoopInHandler();
+
+ @Override
+ public void onPush(InPort port, float demand) {}
+
+ @Override
+ public void onUpstreamFinish(InPort port, Throwable cause) {}
+ }
+}
diff --git a/opendc-simulator/opendc-simulator-flow/src/main/java/org/opendc/simulator/flow2/InPort.java b/opendc-simulator/opendc-simulator-flow/src/main/java/org/opendc/simulator/flow2/InPort.java
new file mode 100644
index 00000000..fba12aaf
--- /dev/null
+++ b/opendc-simulator/opendc-simulator-flow/src/main/java/org/opendc/simulator/flow2/InPort.java
@@ -0,0 +1,214 @@
+/*
+ * Copyright (c) 2022 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.flow2;
+
+import java.time.Clock;
+import java.util.Objects;
+
+/**
+ * A port that consumes a flow.
+ * <p>
+ * Input ports are represented as in-going edges in the flow graph.
+ */
+public final class InPort implements Inlet {
+ private final int id;
+
+ private float capacity;
+ private float demand;
+
+ private boolean mask;
+
+ OutPort output;
+ private InHandler handler = InHandlers.noop();
+ private final Clock clock;
+ private final String name;
+ private final FlowStage stage;
+
+ InPort(FlowStage stage, String name, int id) {
+ this.name = name;
+ this.id = id;
+ this.stage = stage;
+ this.clock = stage.clock;
+ }
+
+ @Override
+ public FlowGraph getGraph() {
+ return stage.parentGraph;
+ }
+
+ @Override
+ public String getName() {
+ return name;
+ }
+
+ /**
+ * Return the identifier of the {@link InPort} with respect to its stage.
+ */
+ public int getId() {
+ return id;
+ }
+
+ /**
+ * Return the current capacity of the input port.
+ */
+ public float getCapacity() {
+ return capacity;
+ }
+
+ /**
+ * Return the current demand of flow of the input port.
+ */
+ public float getDemand() {
+ return demand;
+ }
+
+ /**
+ * Return the current rate of flow of the input port.
+ */
+ public float getRate() {
+ return handler.getRate(this);
+ }
+
+ /**
+ * Pull the flow with the specified <code>capacity</code> from the input port.
+ *
+ * @param capacity The maximum throughput that the stage can receive from the input port.
+ */
+ public void pull(float capacity) {
+ this.capacity = capacity;
+
+ OutPort output = this.output;
+ if (output != null) {
+ output.pull(capacity);
+ }
+ }
+
+ /**
+ * Return the current {@link InHandler} of the input port.
+ */
+ public InHandler getHandler() {
+ return handler;
+ }
+
+ /**
+ * Set the {@link InHandler} of the input port.
+ */
+ public void setHandler(InHandler handler) {
+ this.handler = handler;
+ }
+
+ /**
+ * Return the mask of this port.
+ * <p>
+ * Stages ignore events originating from masked ports.
+ */
+ public boolean getMask() {
+ return mask;
+ }
+
+ /**
+ * (Un)mask the port.
+ */
+ public void setMask(boolean mask) {
+ this.mask = mask;
+ }
+
+ /**
+ * Disconnect the input port from its (potentially) connected outlet.
+ * <p>
+ * The inlet can still be used and re-connected to another outlet.
+ *
+ * @param cause The cause for disconnecting the port or <code>null</code> when no more flow is needed.
+ */
+ public void cancel(Throwable cause) {
+ demand = 0.f;
+
+ OutPort output = this.output;
+ if (output != null) {
+ this.output = null;
+ output.input = null;
+ output.cancel(cause);
+ }
+ }
+
+ @Override
+ public boolean equals(Object o) {
+ if (this == o) return true;
+ if (o == null || getClass() != o.getClass()) return false;
+ InPort port = (InPort) o;
+ return stage.equals(port.stage) && name.equals(port.name);
+ }
+
+ @Override
+ public int hashCode() {
+ return Objects.hash(stage.parentGraph, name);
+ }
+
+ /**
+ * This method is invoked when the inlet is connected to an outlet.
+ */
+ void connect() {
+ OutPort output = this.output;
+ output.pull(capacity);
+ }
+
+ /**
+ * Push a flow from an outlet to this inlet.
+ *
+ * @param demand The rate of flow to push.
+ */
+ void push(float demand) {
+ // No-op when the rate is unchanged
+ if (this.demand == demand) {
+ return;
+ }
+
+ try {
+ handler.onPush(this, demand);
+ this.demand = demand;
+
+ if (!mask) {
+ stage.invalidate(clock.millis());
+ }
+ } catch (Exception e) {
+ stage.doFail(e);
+ }
+ }
+
+ /**
+ * This method is invoked by the connected {@link OutPort} when it finishes.
+ */
+ void finish(Throwable cause) {
+ try {
+ long now = clock.millis();
+ handler.onUpstreamFinish(this, cause);
+ this.demand = 0.f;
+
+ if (!mask) {
+ stage.invalidate(now);
+ }
+ } catch (Exception e) {
+ stage.doFail(e);
+ }
+ }
+}
diff --git a/opendc-simulator/opendc-simulator-compute/src/main/kotlin/org/opendc/simulator/compute/power/ConstantPowerModel.kt b/opendc-simulator/opendc-simulator-flow/src/main/java/org/opendc/simulator/flow2/Inlet.java
index 0fe32b0d..4a9ea6a5 100644
--- a/opendc-simulator/opendc-simulator-compute/src/main/kotlin/org/opendc/simulator/compute/power/ConstantPowerModel.kt
+++ b/opendc-simulator/opendc-simulator-flow/src/main/java/org/opendc/simulator/flow2/Inlet.java
@@ -1,5 +1,5 @@
/*
- * Copyright (c) 2021 AtLarge Research
+ * Copyright (c) 2022 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,19 @@
* SOFTWARE.
*/
-package org.opendc.simulator.compute.power
+package org.opendc.simulator.flow2;
/**
- * A power model which produces a constant value [power].
+ * An in-going edge in a {@link FlowGraph}.
*/
-public class ConstantPowerModel(private val power: Double) : PowerModel {
- public override fun computePower(utilization: Double): Double = power
+public interface Inlet {
+ /**
+ * Return the {@link FlowGraph} to which the inlet is exposed.
+ */
+ FlowGraph getGraph();
- override fun toString(): String = "ConstantPowerModel[power=$power]"
+ /**
+ * Return the name of the inlet.
+ */
+ String getName();
}
diff --git a/opendc-simulator/opendc-simulator-flow/src/main/java/org/opendc/simulator/flow2/InvocationStack.java b/opendc-simulator/opendc-simulator-flow/src/main/java/org/opendc/simulator/flow2/InvocationStack.java
new file mode 100644
index 00000000..a5b5114b
--- /dev/null
+++ b/opendc-simulator/opendc-simulator-flow/src/main/java/org/opendc/simulator/flow2/InvocationStack.java
@@ -0,0 +1,95 @@
+/*
+ * Copyright (c) 2022 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.flow2;
+
+import java.util.Arrays;
+
+/**
+ * A specialized monotonic stack implementation for tracking the scheduled engine invocations.
+ * <p>
+ * By using a specialized class, we reduce the overhead caused by type-erasure.
+ */
+final class InvocationStack {
+ /**
+ * The array of elements in the stack.
+ */
+ private long[] elements;
+
+ private int head = -1;
+
+ public InvocationStack(int initialCapacity) {
+ elements = new long[initialCapacity];
+ Arrays.fill(elements, Long.MIN_VALUE);
+ }
+
+ /**
+ * Try to add the specified invocation to the monotonic stack.
+ *
+ * @param invocation The timestamp of the invocation.
+ * @return <code>true</code> if the invocation was added, <code>false</code> otherwise.
+ */
+ boolean tryAdd(long invocation) {
+ final long[] es = elements;
+ int head = this.head;
+
+ if (head < 0 || es[head] > invocation) {
+ es[head + 1] = invocation;
+ this.head = head + 1;
+
+ if (head + 2 == es.length) {
+ doubleCapacity();
+ }
+
+ return true;
+ }
+
+ return false;
+ }
+
+ /**
+ * Remove the head invocation from the stack or return {@link Long#MAX_VALUE} if the stack is empty.
+ */
+ long poll() {
+ final long[] es = elements;
+ int head = this.head--;
+
+ if (head >= 0) {
+ return es[head];
+ }
+
+ return Long.MAX_VALUE;
+ }
+
+ /**
+ * Doubles the capacity of this deque
+ */
+ private void doubleCapacity() {
+ int oldCapacity = elements.length;
+ int newCapacity = oldCapacity + (oldCapacity >> 1);
+ if (newCapacity < 0) {
+ throw new IllegalStateException("Sorry, deque too big");
+ }
+
+ elements = Arrays.copyOf(elements, newCapacity);
+ }
+}
diff --git a/opendc-simulator/opendc-simulator-flow/src/main/java/org/opendc/simulator/flow2/OutHandler.java b/opendc-simulator/opendc-simulator-flow/src/main/java/org/opendc/simulator/flow2/OutHandler.java
new file mode 100644
index 00000000..723c6d6b
--- /dev/null
+++ b/opendc-simulator/opendc-simulator-flow/src/main/java/org/opendc/simulator/flow2/OutHandler.java
@@ -0,0 +1,47 @@
+/*
+ * Copyright (c) 2022 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.flow2;
+
+/**
+ * Collection of callbacks for the output port (a {@link OutPort}) of a {@link FlowStageLogic}.
+ */
+public interface OutHandler {
+ /**
+ * This method is invoked when another {@link FlowStageLogic} changes the capacity of the outlet.
+ *
+ * @param port The output port of which the capacity was changed.
+ * @param capacity The new capacity of the outlet.
+ */
+ void onPull(OutPort port, float capacity);
+
+ /**
+ * This method is invoked when the output port no longer accepts any flow.
+ * <p>
+ * After this callback no other callbacks will be called for this port.
+ *
+ * @param port The outlet that no longer accepts any flow.
+ * @param cause The cause of the output port no longer accepting any flow or <code>null</code> if the port closed
+ * successfully.
+ */
+ void onDownstreamFinish(OutPort port, Throwable cause);
+}
diff --git a/opendc-simulator/opendc-simulator-flow/src/main/java/org/opendc/simulator/flow2/OutHandlers.java b/opendc-simulator/opendc-simulator-flow/src/main/java/org/opendc/simulator/flow2/OutHandlers.java
new file mode 100644
index 00000000..8fbfda0d
--- /dev/null
+++ b/opendc-simulator/opendc-simulator-flow/src/main/java/org/opendc/simulator/flow2/OutHandlers.java
@@ -0,0 +1,53 @@
+/*
+ * Copyright (c) 2022 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.flow2;
+
+/**
+ * A collection of common {@link OutHandler} implementations.
+ */
+public class OutHandlers {
+ /**
+ * Prevent construction of this class.
+ */
+ private OutHandlers() {}
+
+ /**
+ * Return an {@link OutHandler} that does nothing.
+ */
+ public static OutHandler noop() {
+ return NoopOutHandler.INSTANCE;
+ }
+
+ /**
+ * No-op implementation of {@link OutHandler}.
+ */
+ private static final class NoopOutHandler implements OutHandler {
+ public static final OutHandler INSTANCE = new NoopOutHandler();
+
+ @Override
+ public void onPull(OutPort port, float capacity) {}
+
+ @Override
+ public void onDownstreamFinish(OutPort port, Throwable cause) {}
+ }
+}
diff --git a/opendc-simulator/opendc-simulator-flow/src/main/java/org/opendc/simulator/flow2/OutPort.java b/opendc-simulator/opendc-simulator-flow/src/main/java/org/opendc/simulator/flow2/OutPort.java
new file mode 100644
index 00000000..332296a0
--- /dev/null
+++ b/opendc-simulator/opendc-simulator-flow/src/main/java/org/opendc/simulator/flow2/OutPort.java
@@ -0,0 +1,224 @@
+/*
+ * Copyright (c) 2022 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.flow2;
+
+import java.time.Clock;
+import java.util.Objects;
+
+/**
+ * A port that outputs a flow.
+ * <p>
+ * Output ports are represented as out-going edges in the flow graph.
+ */
+public final class OutPort implements Outlet {
+ private final int id;
+
+ private float capacity;
+ private float demand;
+
+ private boolean mask;
+
+ InPort input;
+ private OutHandler handler = OutHandlers.noop();
+ private final String name;
+ private final FlowStage stage;
+ private final Clock clock;
+
+ OutPort(FlowStage stage, String name, int id) {
+ this.name = name;
+ this.id = id;
+ this.stage = stage;
+ this.clock = stage.clock;
+ }
+
+ @Override
+ public FlowGraph getGraph() {
+ return stage.parentGraph;
+ }
+
+ @Override
+ public String getName() {
+ return name;
+ }
+
+ /**
+ * Return the identifier of the {@link OutPort} with respect to its stage.
+ */
+ public int getId() {
+ return id;
+ }
+
+ /**
+ * Return the capacity of the output port.
+ */
+ public float getCapacity() {
+ return capacity;
+ }
+
+ /**
+ * Return the current demand of flow of the output port.
+ */
+ public float getDemand() {
+ return demand;
+ }
+
+ /**
+ * Return the current rate of flow of the input port.
+ */
+ public float getRate() {
+ InPort input = this.input;
+ if (input != null) {
+ return input.getRate();
+ }
+
+ return 0.f;
+ }
+
+ /**
+ * Return the current {@link OutHandler} of the output port.
+ */
+ public OutHandler getHandler() {
+ return handler;
+ }
+
+ /**
+ * Set the {@link OutHandler} of the output port.
+ */
+ public void setHandler(OutHandler handler) {
+ this.handler = handler;
+ }
+
+ /**
+ * Return the mask of this port.
+ * <p>
+ * Stages ignore events originating from masked ports.
+ */
+ public boolean getMask() {
+ return mask;
+ }
+
+ /**
+ * (Un)mask the port.
+ */
+ public void setMask(boolean mask) {
+ this.mask = mask;
+ }
+
+ /**
+ * Push the given flow rate over output port.
+ *
+ * @param rate The rate of the flow to push.
+ */
+ public void push(float rate) {
+ demand = rate;
+ InPort input = this.input;
+
+ if (input != null) {
+ input.push(rate);
+ }
+ }
+
+ /**
+ * Signal to the downstream port that the output has completed successfully and disconnect the port from its input.
+ * <p>
+ * The output port can still be used and re-connected to another input.
+ */
+ public void complete() {
+ fail(null);
+ }
+
+ /**
+ * Signal a failure to the downstream port and disconnect the port from its input.
+ * <p>
+ * The output can still be used and re-connected to another input.
+ */
+ public void fail(Throwable cause) {
+ capacity = 0.f;
+
+ InPort input = this.input;
+ if (input != null) {
+ this.input = null;
+ input.output = null;
+ input.finish(cause);
+ }
+ }
+
+ @Override
+ public boolean equals(Object o) {
+ if (this == o) return true;
+ if (o == null || getClass() != o.getClass()) return false;
+ OutPort port = (OutPort) o;
+ return stage.equals(port.stage) && name.equals(port.name);
+ }
+
+ @Override
+ public int hashCode() {
+ return Objects.hash(stage.parentGraph, name);
+ }
+
+ /**
+ * This method is invoked when the outlet is connected to an inlet.
+ */
+ void connect() {
+ input.push(demand);
+ }
+
+ /**
+ * Pull from this outlet with a specified capacity.
+ *
+ * @param capacity The capacity of the inlet.
+ */
+ void pull(float capacity) {
+ // No-op when outlet is not active or the rate is unchanged
+ if (this.capacity == capacity) {
+ return;
+ }
+
+ try {
+ handler.onPull(this, capacity);
+ this.capacity = capacity;
+
+ if (!mask) {
+ stage.invalidate(clock.millis());
+ }
+ } catch (Exception e) {
+ stage.doFail(e);
+ }
+ }
+
+ /**
+ * This method is invoked by the connected {@link InPort} when downstream cancels the connection.
+ */
+ void cancel(Throwable cause) {
+ try {
+ handler.onDownstreamFinish(this, cause);
+ this.capacity = 0.f;
+
+ if (!mask) {
+ stage.invalidate(clock.millis());
+ }
+ } catch (Exception e) {
+ stage.doFail(e);
+ }
+ }
+}
diff --git a/opendc-simulator/opendc-simulator-flow/src/main/java/org/opendc/simulator/flow2/Outlet.java b/opendc-simulator/opendc-simulator-flow/src/main/java/org/opendc/simulator/flow2/Outlet.java
new file mode 100644
index 00000000..32e19a3b
--- /dev/null
+++ b/opendc-simulator/opendc-simulator-flow/src/main/java/org/opendc/simulator/flow2/Outlet.java
@@ -0,0 +1,38 @@
+/*
+ * Copyright (c) 2022 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.flow2;
+
+/**
+ * An out-going edge in a {@link FlowGraph}.
+ */
+public interface Outlet {
+ /**
+ * Return the {@link FlowGraph} to which the outlet is exposed.
+ */
+ FlowGraph getGraph();
+
+ /**
+ * Return the name of the outlet.
+ */
+ String getName();
+}
diff --git a/opendc-simulator/opendc-simulator-flow/src/main/java/org/opendc/simulator/flow2/mux/FlowMultiplexer.java b/opendc-simulator/opendc-simulator-flow/src/main/java/org/opendc/simulator/flow2/mux/FlowMultiplexer.java
new file mode 100644
index 00000000..dec98955
--- /dev/null
+++ b/opendc-simulator/opendc-simulator-flow/src/main/java/org/opendc/simulator/flow2/mux/FlowMultiplexer.java
@@ -0,0 +1,95 @@
+/*
+ * Copyright (c) 2022 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.flow2.mux;
+
+import org.opendc.simulator.flow2.FlowStageLogic;
+import org.opendc.simulator.flow2.Inlet;
+import org.opendc.simulator.flow2.Outlet;
+
+/**
+ * A {@link FlowStageLogic} that multiplexes multiple inputs over (possibly) multiple outputs.
+ */
+public interface FlowMultiplexer {
+ /**
+ * Return maximum number of inputs supported by the multiplexer.
+ */
+ int getMaxInputs();
+
+ /**
+ * Return maximum number of outputs supported by the multiplexer.
+ */
+ int getMaxOutputs();
+
+ /**
+ * Return the number of active inputs on this multiplexer.
+ */
+ int getInputCount();
+
+ /**
+ * Allocate a new input on this multiplexer with the specified capacity..
+ *
+ * @return The identifier of the input for this stage.
+ */
+ Inlet newInput();
+
+ /**
+ * Release the input at the specified slot.
+ *
+ * @param inlet The inlet to release.
+ */
+ void releaseInput(Inlet inlet);
+
+ /**
+ * Return the number of active outputs on this multiplexer.
+ */
+ int getOutputCount();
+
+ /**
+ * Allocate a new output on this multiplexer.
+ *
+ * @return The outlet for this stage.
+ */
+ Outlet newOutput();
+
+ /**
+ * Release the output at the specified slot.
+ *
+ * @param outlet The outlet to release.
+ */
+ void releaseOutput(Outlet outlet);
+
+ /**
+ * Return the total input capacity of the {@link FlowMultiplexer}.
+ */
+ float getCapacity();
+
+ /**
+ * Return the total input demand for the {@link FlowMultiplexer}.
+ */
+ float getDemand();
+
+ /**
+ * Return the total input rate for the {@link FlowMultiplexer}.
+ */
+ float getRate();
+}
diff --git a/opendc-simulator/opendc-simulator-compute/src/main/kotlin/org/opendc/simulator/compute/SimMachineContext.kt b/opendc-simulator/opendc-simulator-flow/src/main/java/org/opendc/simulator/flow2/mux/FlowMultiplexerFactory.java
index 5e3a7766..0b5b9141 100644
--- a/opendc-simulator/opendc-simulator-compute/src/main/kotlin/org/opendc/simulator/compute/SimMachineContext.kt
+++ b/opendc-simulator/opendc-simulator-flow/src/main/java/org/opendc/simulator/flow2/mux/FlowMultiplexerFactory.java
@@ -1,5 +1,5 @@
/*
- * Copyright (c) 2020 AtLarge Research
+ * Copyright (c) 2022 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,41 +20,32 @@
* SOFTWARE.
*/
-package org.opendc.simulator.compute
+package org.opendc.simulator.flow2.mux;
+
+import org.opendc.simulator.flow2.FlowGraph;
/**
- * 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.
+ * Factory interface for a {@link FlowMultiplexer} implementation.
*/
-public interface SimMachineContext : AutoCloseable {
- /**
- * The metadata associated with the context.
- */
- public val meta: Map<String, Any>
-
- /**
- * The CPUs available on the machine.
- */
- public val cpus: List<SimProcessingUnit>
-
- /**
- * The memory interface of the machine.
- */
- public val memory: SimMemory
-
+public interface FlowMultiplexerFactory {
/**
- * The network interfaces available to the workload.
+ * Construct a new {@link FlowMultiplexer} belonging to the specified {@link FlowGraph}.
+ *
+ * @param graph The graph to which the multiplexer belongs.
*/
- public val net: List<SimNetworkInterface>
+ FlowMultiplexer newMultiplexer(FlowGraph graph);
/**
- * The storage devices available to the workload.
+ * Return a {@link FlowMultiplexerFactory} for {@link ForwardingFlowMultiplexer} instances.
*/
- public val storage: List<SimStorageInterface>
+ static FlowMultiplexerFactory forwardingMultiplexer() {
+ return ForwardingFlowMultiplexer.FACTORY;
+ }
/**
- * Stop the workload.
+ * Return a {@link FlowMultiplexerFactory} for {@link MaxMinFlowMultiplexer} instances.
*/
- public override fun close()
+ static FlowMultiplexerFactory maxMinMultiplexer() {
+ return MaxMinFlowMultiplexer.FACTORY;
+ }
}
diff --git a/opendc-simulator/opendc-simulator-flow/src/main/java/org/opendc/simulator/flow2/mux/ForwardingFlowMultiplexer.java b/opendc-simulator/opendc-simulator-flow/src/main/java/org/opendc/simulator/flow2/mux/ForwardingFlowMultiplexer.java
new file mode 100644
index 00000000..abe3510b
--- /dev/null
+++ b/opendc-simulator/opendc-simulator-flow/src/main/java/org/opendc/simulator/flow2/mux/ForwardingFlowMultiplexer.java
@@ -0,0 +1,280 @@
+/*
+ * Copyright (c) 2022 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.flow2.mux;
+
+import java.util.Arrays;
+import java.util.BitSet;
+import org.opendc.simulator.flow2.FlowGraph;
+import org.opendc.simulator.flow2.FlowStage;
+import org.opendc.simulator.flow2.FlowStageLogic;
+import org.opendc.simulator.flow2.InHandler;
+import org.opendc.simulator.flow2.InPort;
+import org.opendc.simulator.flow2.Inlet;
+import org.opendc.simulator.flow2.OutHandler;
+import org.opendc.simulator.flow2.OutPort;
+import org.opendc.simulator.flow2.Outlet;
+
+/**
+ * A {@link 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.
+ */
+public final class ForwardingFlowMultiplexer implements FlowMultiplexer, FlowStageLogic {
+ /**
+ * Factory implementation for this implementation.
+ */
+ static FlowMultiplexerFactory FACTORY = ForwardingFlowMultiplexer::new;
+
+ public final IdleInHandler IDLE_IN_HANDLER = new IdleInHandler();
+ public final IdleOutHandler IDLE_OUT_HANDLER = new IdleOutHandler();
+
+ private final FlowStage stage;
+
+ private InPort[] inlets;
+ private OutPort[] outlets;
+ private final BitSet activeInputs;
+ private final BitSet activeOutputs;
+ private final BitSet availableOutputs;
+
+ private float capacity = 0.f;
+ private float demand = 0.f;
+
+ public ForwardingFlowMultiplexer(FlowGraph graph) {
+ this.stage = graph.newStage(this);
+
+ this.inlets = new InPort[4];
+ this.activeInputs = new BitSet();
+ this.outlets = new OutPort[4];
+ this.activeOutputs = new BitSet();
+ this.availableOutputs = new BitSet();
+ }
+
+ @Override
+ public float getCapacity() {
+ return capacity;
+ }
+
+ @Override
+ public float getDemand() {
+ return demand;
+ }
+
+ @Override
+ public float getRate() {
+ final BitSet activeOutputs = this.activeOutputs;
+ final OutPort[] outlets = this.outlets;
+ float rate = 0.f;
+ for (int i = activeOutputs.nextSetBit(0); i != -1; i = activeOutputs.nextSetBit(i + 1)) {
+ rate += outlets[i].getRate();
+ }
+ return rate;
+ }
+
+ @Override
+ public int getMaxInputs() {
+ return getOutputCount();
+ }
+
+ @Override
+ public int getMaxOutputs() {
+ return Integer.MAX_VALUE;
+ }
+
+ @Override
+ public int getInputCount() {
+ return activeInputs.length();
+ }
+
+ @Override
+ public Inlet newInput() {
+ final BitSet activeInputs = this.activeInputs;
+ int slot = activeInputs.nextClearBit(0);
+
+ InPort inPort = stage.getInlet("in" + slot);
+ inPort.setMask(true);
+
+ InPort[] inlets = this.inlets;
+ if (slot >= inlets.length) {
+ int newLength = inlets.length + (inlets.length >> 1);
+ inlets = Arrays.copyOf(inlets, newLength);
+ this.inlets = inlets;
+ }
+
+ final BitSet availableOutputs = this.availableOutputs;
+ int outSlot = availableOutputs.nextSetBit(0);
+
+ if (outSlot < 0) {
+ throw new IllegalStateException("No capacity available for a new input");
+ }
+
+ inlets[slot] = inPort;
+ activeInputs.set(slot);
+
+ OutPort outPort = outlets[outSlot];
+ availableOutputs.clear(outSlot);
+
+ inPort.setHandler(new ForwardingInHandler(outPort));
+ outPort.setHandler(new ForwardingOutHandler(inPort));
+
+ inPort.pull(outPort.getCapacity());
+
+ return inPort;
+ }
+
+ @Override
+ public void releaseInput(Inlet inlet) {
+ InPort port = (InPort) inlet;
+ int slot = port.getId();
+
+ final BitSet activeInputs = this.activeInputs;
+
+ if (!activeInputs.get(slot)) {
+ return;
+ }
+
+ port.cancel(null);
+ activeInputs.clear(slot);
+
+ ForwardingInHandler inHandler = (ForwardingInHandler) port.getHandler();
+ availableOutputs.set(inHandler.output.getId());
+
+ port.setHandler(IDLE_IN_HANDLER);
+ }
+
+ @Override
+ public int getOutputCount() {
+ return activeOutputs.length();
+ }
+
+ @Override
+ public Outlet newOutput() {
+ final BitSet activeOutputs = this.activeOutputs;
+ int slot = activeOutputs.nextClearBit(0);
+
+ OutPort port = stage.getOutlet("out" + slot);
+ OutPort[] outlets = this.outlets;
+ if (slot >= outlets.length) {
+ int newLength = outlets.length + (outlets.length >> 1);
+ outlets = Arrays.copyOf(outlets, newLength);
+ this.outlets = outlets;
+ }
+ outlets[slot] = port;
+
+ activeOutputs.set(slot);
+ availableOutputs.set(slot);
+ return port;
+ }
+
+ @Override
+ public void releaseOutput(Outlet outlet) {
+ OutPort port = (OutPort) outlet;
+ int slot = port.getId();
+ activeInputs.clear(slot);
+ availableOutputs.clear(slot);
+ port.complete();
+
+ port.setHandler(IDLE_OUT_HANDLER);
+ }
+
+ @Override
+ public long onUpdate(FlowStage ctx, long now) {
+ return Long.MAX_VALUE;
+ }
+
+ class ForwardingInHandler implements InHandler {
+ final OutPort output;
+
+ ForwardingInHandler(OutPort output) {
+ this.output = output;
+ }
+
+ @Override
+ public float getRate(InPort port) {
+ return output.getRate();
+ }
+
+ @Override
+ public void onPush(InPort port, float rate) {
+ ForwardingFlowMultiplexer.this.demand += -port.getDemand() + rate;
+
+ output.push(rate);
+ }
+
+ @Override
+ public void onUpstreamFinish(InPort port, Throwable cause) {
+ ForwardingFlowMultiplexer.this.demand -= port.getDemand();
+
+ final OutPort output = this.output;
+ output.push(0.f);
+
+ releaseInput(port);
+ }
+ }
+
+ private class ForwardingOutHandler implements OutHandler {
+ private final InPort input;
+
+ ForwardingOutHandler(InPort input) {
+ this.input = input;
+ }
+
+ @Override
+ public void onPull(OutPort port, float capacity) {
+ ForwardingFlowMultiplexer.this.capacity += -port.getCapacity() + capacity;
+
+ input.pull(capacity);
+ }
+
+ @Override
+ public void onDownstreamFinish(OutPort port, Throwable cause) {
+ ForwardingFlowMultiplexer.this.capacity -= port.getCapacity();
+
+ input.cancel(cause);
+
+ releaseOutput(port);
+ }
+ }
+
+ private static class IdleInHandler implements InHandler {
+ @Override
+ public float getRate(InPort port) {
+ return 0.f;
+ }
+
+ @Override
+ public void onPush(InPort port, float rate) {
+ port.cancel(new IllegalStateException("Inlet is not allocated"));
+ }
+
+ @Override
+ public void onUpstreamFinish(InPort port, Throwable cause) {}
+ }
+
+ private static class IdleOutHandler implements OutHandler {
+ @Override
+ public void onPull(OutPort port, float capacity) {}
+
+ @Override
+ public void onDownstreamFinish(OutPort port, Throwable cause) {}
+ }
+}
diff --git a/opendc-simulator/opendc-simulator-flow/src/main/java/org/opendc/simulator/flow2/mux/MaxMinFlowMultiplexer.java b/opendc-simulator/opendc-simulator-flow/src/main/java/org/opendc/simulator/flow2/mux/MaxMinFlowMultiplexer.java
new file mode 100644
index 00000000..ac5c4f5c
--- /dev/null
+++ b/opendc-simulator/opendc-simulator-flow/src/main/java/org/opendc/simulator/flow2/mux/MaxMinFlowMultiplexer.java
@@ -0,0 +1,297 @@
+/*
+ * Copyright (c) 2022 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.flow2.mux;
+
+import java.util.Arrays;
+import java.util.BitSet;
+import org.opendc.simulator.flow2.FlowGraph;
+import org.opendc.simulator.flow2.FlowStage;
+import org.opendc.simulator.flow2.FlowStageLogic;
+import org.opendc.simulator.flow2.InHandler;
+import org.opendc.simulator.flow2.InPort;
+import org.opendc.simulator.flow2.Inlet;
+import org.opendc.simulator.flow2.OutHandler;
+import org.opendc.simulator.flow2.OutPort;
+import org.opendc.simulator.flow2.Outlet;
+
+/**
+ * A {@link FlowMultiplexer} implementation that distributes the available capacity of the outputs over the inputs
+ * using max-min fair sharing.
+ * <p>
+ * The max-min fair sharing algorithm of this multiplexer ensures that each input receives a fair share of the combined
+ * output capacity, but allows individual inputs to use more capacity if there is still capacity left.
+ */
+public final class MaxMinFlowMultiplexer implements FlowMultiplexer, FlowStageLogic {
+ /**
+ * Factory implementation for this implementation.
+ */
+ static FlowMultiplexerFactory FACTORY = MaxMinFlowMultiplexer::new;
+
+ private final FlowStage stage;
+ private final BitSet activeInputs;
+ private final BitSet activeOutputs;
+
+ private float capacity = 0.f;
+ private float demand = 0.f;
+ private float rate = 0.f;
+
+ private InPort[] inlets;
+ private long[] inputs;
+ private float[] rates;
+ private OutPort[] outlets;
+
+ private final MultiplexerInHandler inHandler = new MultiplexerInHandler();
+ private final MultiplexerOutHandler outHandler = new MultiplexerOutHandler();
+
+ /**
+ * Construct a {@link MaxMinFlowMultiplexer} instance.
+ *
+ * @param graph The {@link FlowGraph} to add the multiplexer to.
+ */
+ public MaxMinFlowMultiplexer(FlowGraph graph) {
+ this.stage = graph.newStage(this);
+ this.activeInputs = new BitSet();
+ this.activeOutputs = new BitSet();
+
+ this.inlets = new InPort[4];
+ this.inputs = new long[4];
+ this.rates = new float[4];
+ this.outlets = new OutPort[4];
+ }
+
+ @Override
+ public float getCapacity() {
+ return capacity;
+ }
+
+ @Override
+ public float getDemand() {
+ return demand;
+ }
+
+ @Override
+ public float getRate() {
+ return rate;
+ }
+
+ @Override
+ public int getMaxInputs() {
+ return Integer.MAX_VALUE;
+ }
+
+ @Override
+ public int getMaxOutputs() {
+ return Integer.MAX_VALUE;
+ }
+
+ @Override
+ public long onUpdate(FlowStage ctx, long now) {
+ float capacity = this.capacity;
+ float demand = this.demand;
+ float rate = demand;
+
+ if (demand > capacity) {
+ rate = redistributeCapacity(inlets, inputs, rates, capacity);
+ }
+
+ if (this.rate != rate) {
+ // Only update the outputs if the output rate has changed
+ this.rate = rate;
+
+ changeRate(activeOutputs, outlets, capacity, rate);
+ }
+
+ return Long.MAX_VALUE;
+ }
+
+ @Override
+ public int getInputCount() {
+ return activeInputs.length();
+ }
+
+ @Override
+ public Inlet newInput() {
+ final BitSet activeInputs = this.activeInputs;
+ int slot = activeInputs.nextClearBit(0);
+
+ InPort port = stage.getInlet("in" + slot);
+ port.setHandler(inHandler);
+ port.pull(this.capacity);
+
+ InPort[] inlets = this.inlets;
+ if (slot >= inlets.length) {
+ int newLength = inlets.length + (inlets.length >> 1);
+ inlets = Arrays.copyOf(inlets, newLength);
+ inputs = Arrays.copyOf(inputs, newLength);
+ rates = Arrays.copyOf(rates, newLength);
+ this.inlets = inlets;
+ }
+ inlets[slot] = port;
+
+ activeInputs.set(slot);
+ return port;
+ }
+
+ @Override
+ public void releaseInput(Inlet inlet) {
+ InPort port = (InPort) inlet;
+
+ activeInputs.clear(port.getId());
+ port.cancel(null);
+ }
+
+ @Override
+ public int getOutputCount() {
+ return activeOutputs.length();
+ }
+
+ @Override
+ public Outlet newOutput() {
+ final BitSet activeOutputs = this.activeOutputs;
+ int slot = activeOutputs.nextClearBit(0);
+
+ OutPort port = stage.getOutlet("out" + slot);
+ port.setHandler(outHandler);
+
+ OutPort[] outlets = this.outlets;
+ if (slot >= outlets.length) {
+ int newLength = outlets.length + (outlets.length >> 1);
+ outlets = Arrays.copyOf(outlets, newLength);
+ this.outlets = outlets;
+ }
+ outlets[slot] = port;
+
+ activeOutputs.set(slot);
+ return port;
+ }
+
+ @Override
+ public void releaseOutput(Outlet outlet) {
+ OutPort port = (OutPort) outlet;
+ activeInputs.clear(port.getId());
+ port.complete();
+ }
+
+ /**
+ * Helper function to redistribute the specified capacity across the inlets.
+ */
+ private static float redistributeCapacity(InPort[] inlets, long[] inputs, float[] rates, float capacity) {
+ // If the demand is higher than the capacity, we need use max-min fair sharing to distribute the
+ // constrained capacity across the inputs.
+ for (int i = 0; i < inputs.length; i++) {
+ InPort inlet = inlets[i];
+ if (inlet == null) {
+ break;
+ }
+
+ inputs[i] = ((long) Float.floatToRawIntBits(inlet.getDemand()) << 32) | (i & 0xFFFFFFFFL);
+ }
+ Arrays.sort(inputs);
+
+ float availableCapacity = capacity;
+ int inputSize = inputs.length;
+
+ // Divide the available output capacity fairly over the inputs using max-min fair sharing
+ for (int i = 0; i < inputs.length; i++) {
+ long v = inputs[i];
+ int slot = (int) v;
+ float d = Float.intBitsToFloat((int) (v >> 32));
+
+ if (d == 0.0) {
+ continue;
+ }
+
+ float availableShare = availableCapacity / (inputSize - i);
+ float r = Math.min(d, availableShare);
+
+ rates[slot] = r;
+ availableCapacity -= r;
+ }
+
+ return capacity - availableCapacity;
+ }
+
+ /**
+ * Helper method to change the rate of the outlets.
+ */
+ private static void changeRate(BitSet activeOutputs, OutPort[] outlets, float capacity, float rate) {
+ // Divide the requests over the available capacity of the input resources fairly
+ for (int i = activeOutputs.nextSetBit(0); i != -1; i = activeOutputs.nextSetBit(i + 1)) {
+ OutPort outlet = outlets[i];
+ float fraction = outlet.getCapacity() / capacity;
+ outlet.push(rate * fraction);
+ }
+ }
+
+ /**
+ * A {@link InHandler} implementation for the multiplexer inputs.
+ */
+ private class MultiplexerInHandler implements InHandler {
+ @Override
+ public float getRate(InPort port) {
+ return rates[port.getId()];
+ }
+
+ @Override
+ public void onPush(InPort port, float demand) {
+ MaxMinFlowMultiplexer.this.demand += -port.getDemand() + demand;
+ rates[port.getId()] = demand;
+ }
+
+ @Override
+ public void onUpstreamFinish(InPort port, Throwable cause) {
+ MaxMinFlowMultiplexer.this.demand -= port.getDemand();
+ releaseInput(port);
+ rates[port.getId()] = 0.f;
+ }
+ }
+
+ /**
+ * A {@link OutHandler} implementation for the multiplexer outputs.
+ */
+ private class MultiplexerOutHandler implements OutHandler {
+ @Override
+ public void onPull(OutPort port, float capacity) {
+ float newCapacity = MaxMinFlowMultiplexer.this.capacity - port.getCapacity() + capacity;
+ MaxMinFlowMultiplexer.this.capacity = newCapacity;
+ changeInletCapacity(newCapacity);
+ }
+
+ @Override
+ public void onDownstreamFinish(OutPort port, Throwable cause) {
+ float newCapacity = MaxMinFlowMultiplexer.this.capacity - port.getCapacity();
+ MaxMinFlowMultiplexer.this.capacity = newCapacity;
+ releaseOutput(port);
+ changeInletCapacity(newCapacity);
+ }
+
+ private void changeInletCapacity(float capacity) {
+ BitSet activeInputs = MaxMinFlowMultiplexer.this.activeInputs;
+ InPort[] inlets = MaxMinFlowMultiplexer.this.inlets;
+
+ for (int i = activeInputs.nextSetBit(0); i != -1; i = activeInputs.nextSetBit(i + 1)) {
+ inlets[i].pull(capacity);
+ }
+ }
+ }
+}
diff --git a/opendc-simulator/opendc-simulator-flow/src/main/kotlin/org/opendc/simulator/flow/internal/Constants.kt b/opendc-simulator/opendc-simulator-flow/src/main/java/org/opendc/simulator/flow2/sink/FlowSink.java
index 450195ec..69c94708 100644
--- a/opendc-simulator/opendc-simulator-flow/src/main/kotlin/org/opendc/simulator/flow/internal/Constants.kt
+++ b/opendc-simulator/opendc-simulator-flow/src/main/java/org/opendc/simulator/flow2/sink/FlowSink.java
@@ -1,5 +1,5 @@
/*
- * Copyright (c) 2021 AtLarge Research
+ * Copyright (c) 2022 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,9 +20,17 @@
* SOFTWARE.
*/
-package org.opendc.simulator.flow.internal
+package org.opendc.simulator.flow2.sink;
+
+import org.opendc.simulator.flow2.FlowStage;
+import org.opendc.simulator.flow2.Inlet;
/**
- * Constant for converting milliseconds into seconds.
+ * A {@link FlowStage} with a single input.
*/
-internal const val D_MS_TO_S = 1 / 1000.0
+public interface FlowSink {
+ /**
+ * Return the input of this {@link FlowSink}.
+ */
+ Inlet getInput();
+}
diff --git a/opendc-simulator/opendc-simulator-flow/src/main/java/org/opendc/simulator/flow2/sink/SimpleFlowSink.java b/opendc-simulator/opendc-simulator-flow/src/main/java/org/opendc/simulator/flow2/sink/SimpleFlowSink.java
new file mode 100644
index 00000000..fdfe5ee8
--- /dev/null
+++ b/opendc-simulator/opendc-simulator-flow/src/main/java/org/opendc/simulator/flow2/sink/SimpleFlowSink.java
@@ -0,0 +1,123 @@
+/*
+ * Copyright (c) 2022 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.flow2.sink;
+
+import org.opendc.simulator.flow2.FlowGraph;
+import org.opendc.simulator.flow2.FlowStage;
+import org.opendc.simulator.flow2.FlowStageLogic;
+import org.opendc.simulator.flow2.InHandler;
+import org.opendc.simulator.flow2.InPort;
+import org.opendc.simulator.flow2.Inlet;
+
+/**
+ * A sink with a fixed capacity.
+ */
+public final class SimpleFlowSink implements FlowSink, FlowStageLogic {
+ private final FlowStage stage;
+ private final InPort input;
+ private final Handler handler;
+
+ /**
+ * Construct a new {@link SimpleFlowSink} with the specified initial capacity.
+ *
+ * @param graph The graph to add the sink to.
+ * @param initialCapacity The initial capacity of the sink.
+ */
+ public SimpleFlowSink(FlowGraph graph, float initialCapacity) {
+ this.stage = graph.newStage(this);
+ this.handler = new Handler();
+ this.input = stage.getInlet("in");
+ this.input.pull(initialCapacity);
+ this.input.setMask(true);
+ this.input.setHandler(handler);
+ }
+
+ /**
+ * Return the {@link Inlet} of this sink.
+ */
+ @Override
+ public Inlet getInput() {
+ return input;
+ }
+
+ /**
+ * Return the capacity of the sink.
+ */
+ public float getCapacity() {
+ return input.getCapacity();
+ }
+
+ /**
+ * Update the capacity of the sink.
+ *
+ * @param capacity The new capacity to update the sink to.
+ */
+ public void setCapacity(float capacity) {
+ input.pull(capacity);
+ stage.invalidate();
+ }
+
+ /**
+ * Return the flow rate of the sink.
+ */
+ public float getRate() {
+ return input.getRate();
+ }
+
+ /**
+ * Remove this node from the graph.
+ */
+ public void close() {
+ stage.close();
+ }
+
+ @Override
+ public long onUpdate(FlowStage ctx, long now) {
+ InPort input = this.input;
+ handler.rate = Math.min(input.getDemand(), input.getCapacity());
+ return Long.MAX_VALUE;
+ }
+
+ /**
+ * The {@link InHandler} implementation for the sink.
+ */
+ private static final class Handler implements InHandler {
+ float rate;
+
+ @Override
+ public float getRate(InPort port) {
+ return rate;
+ }
+
+ @Override
+ public void onPush(InPort port, float demand) {
+ float capacity = port.getCapacity();
+ rate = Math.min(demand, capacity);
+ }
+
+ @Override
+ public void onUpstreamFinish(InPort port, Throwable cause) {
+ rate = 0.f;
+ }
+ }
+}
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/java/org/opendc/simulator/flow2/source/EmptyFlowSource.java
index 98922ab3..2dcc66e4 100644
--- a/opendc-simulator/opendc-simulator-flow/src/main/kotlin/org/opendc/simulator/flow/FlowConsumerContext.kt
+++ b/opendc-simulator/opendc-simulator-flow/src/main/java/org/opendc/simulator/flow2/source/EmptyFlowSource.java
@@ -1,5 +1,5 @@
/*
- * Copyright (c) 2021 AtLarge Research
+ * Copyright (c) 2022 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,43 +20,46 @@
* SOFTWARE.
*/
-package org.opendc.simulator.flow
+package org.opendc.simulator.flow2.source;
+
+import org.opendc.simulator.flow2.FlowGraph;
+import org.opendc.simulator.flow2.FlowStage;
+import org.opendc.simulator.flow2.FlowStageLogic;
+import org.opendc.simulator.flow2.OutPort;
+import org.opendc.simulator.flow2.Outlet;
/**
- * A controllable [FlowConnection].
- *
- * This interface is used by [FlowConsumer]s to control the connection between it and the source.
+ * An empty {@link FlowSource}.
*/
-public interface FlowConsumerContext : FlowConnection {
- /**
- * The deadline of the source.
- */
- public val deadline: Long
+public final class EmptyFlowSource implements FlowSource, FlowStageLogic {
+ private final FlowStage stage;
+ private final OutPort output;
/**
- * The capacity of the connection.
+ * Construct a new {@link EmptyFlowSource}.
*/
- public override var capacity: Double
+ public EmptyFlowSource(FlowGraph graph) {
+ this.stage = graph.newStage(this);
+ this.output = stage.getOutlet("out");
+ }
/**
- * A flag to control whether [FlowConsumerLogic.onConverge] should be invoked for the consumer.
+ * Return the {@link Outlet} of the source.
*/
- public var shouldConsumerConverge: Boolean
+ @Override
+ public Outlet getOutput() {
+ return output;
+ }
/**
- * A flag to control whether the timers for the [FlowSource] should be enabled.
+ * Remove this node from the graph.
*/
- public var enableTimers: Boolean
+ public void close() {
+ stage.close();
+ }
- /**
- * 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)
+ @Override
+ public long onUpdate(FlowStage ctx, long now) {
+ return Long.MAX_VALUE;
+ }
}
diff --git a/opendc-simulator/opendc-simulator-flow/src/main/java/org/opendc/simulator/flow2/source/FlowSource.java b/opendc-simulator/opendc-simulator-flow/src/main/java/org/opendc/simulator/flow2/source/FlowSource.java
new file mode 100644
index 00000000..f9432c33
--- /dev/null
+++ b/opendc-simulator/opendc-simulator-flow/src/main/java/org/opendc/simulator/flow2/source/FlowSource.java
@@ -0,0 +1,36 @@
+/*
+ * Copyright (c) 2022 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.flow2.source;
+
+import org.opendc.simulator.flow2.FlowStage;
+import org.opendc.simulator.flow2.Outlet;
+
+/**
+ * A {@link FlowStage} with a single output.
+ */
+public interface FlowSource {
+ /**
+ * Return the output of this {@link FlowSource}.
+ */
+ Outlet getOutput();
+}
diff --git a/opendc-simulator/opendc-simulator-flow/src/main/java/org/opendc/simulator/flow2/source/RuntimeFlowSource.java b/opendc-simulator/opendc-simulator-flow/src/main/java/org/opendc/simulator/flow2/source/RuntimeFlowSource.java
new file mode 100644
index 00000000..c09987cd
--- /dev/null
+++ b/opendc-simulator/opendc-simulator-flow/src/main/java/org/opendc/simulator/flow2/source/RuntimeFlowSource.java
@@ -0,0 +1,128 @@
+/*
+ * Copyright (c) 2022 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.flow2.source;
+
+import java.util.function.Consumer;
+import org.opendc.simulator.flow2.FlowGraph;
+import org.opendc.simulator.flow2.FlowStage;
+import org.opendc.simulator.flow2.FlowStageLogic;
+import org.opendc.simulator.flow2.OutHandler;
+import org.opendc.simulator.flow2.OutPort;
+import org.opendc.simulator.flow2.Outlet;
+
+/**
+ * A {@link FlowSource} that ensures a flow is emitted for a specified amount of time at some utilization.
+ */
+public class RuntimeFlowSource implements FlowSource, FlowStageLogic {
+ private final float utilization;
+
+ private final FlowStage stage;
+ private final OutPort output;
+ private final Consumer<RuntimeFlowSource> completionHandler;
+
+ private long duration;
+ private long lastPull;
+
+ /**
+ * Construct a {@link RuntimeFlowSource} instance.
+ *
+ * @param graph The {@link FlowGraph} to which this source belongs.
+ * @param duration The duration of the source.
+ * @param utilization The utilization of the capacity of the outlet.
+ * @param completionHandler A callback invoked when the source completes.
+ */
+ public RuntimeFlowSource(
+ FlowGraph graph, long duration, float utilization, Consumer<RuntimeFlowSource> completionHandler) {
+ if (duration <= 0) {
+ throw new IllegalArgumentException("Duration must be positive and non-zero");
+ }
+
+ if (utilization <= 0.0) {
+ throw new IllegalArgumentException("Utilization must be positive and non-zero");
+ }
+
+ this.stage = graph.newStage(this);
+ this.output = stage.getOutlet("out");
+ this.output.setHandler(new OutHandler() {
+ @Override
+ public void onPull(OutPort port, float capacity) {}
+
+ @Override
+ public void onDownstreamFinish(OutPort port, Throwable cause) {
+ // Source cannot complete without re-connecting to another sink, so mark the source as completed
+ completionHandler.accept(RuntimeFlowSource.this);
+ }
+ });
+ this.duration = duration;
+ this.utilization = utilization;
+ this.completionHandler = completionHandler;
+ this.lastPull = graph.getEngine().getClock().millis();
+ }
+
+ /**
+ * Construct a new {@link RuntimeFlowSource}.
+ *
+ * @param graph The {@link FlowGraph} to which this source belongs.
+ * @param duration The duration of the source.
+ * @param utilization The utilization of the capacity of the outlet.
+ */
+ public RuntimeFlowSource(FlowGraph graph, long duration, float utilization) {
+ this(graph, duration, utilization, RuntimeFlowSource::close);
+ }
+
+ /**
+ * Return the {@link Outlet} of the source.
+ */
+ @Override
+ public Outlet getOutput() {
+ return output;
+ }
+
+ /**
+ * Remove this node from the graph.
+ */
+ public void close() {
+ stage.close();
+ }
+
+ @Override
+ public long onUpdate(FlowStage ctx, long now) {
+ long lastPull = this.lastPull;
+ this.lastPull = now;
+
+ long delta = Math.max(0, now - lastPull);
+
+ OutPort output = this.output;
+ float limit = output.getCapacity() * utilization;
+ long duration = this.duration - delta;
+
+ if (duration <= 0) {
+ completionHandler.accept(this);
+ return Long.MAX_VALUE;
+ }
+
+ this.duration = duration;
+ output.push(limit);
+ return now + duration;
+ }
+}
diff --git a/opendc-simulator/opendc-simulator-flow/src/main/java/org/opendc/simulator/flow2/source/SimpleFlowSource.java b/opendc-simulator/opendc-simulator-flow/src/main/java/org/opendc/simulator/flow2/source/SimpleFlowSource.java
new file mode 100644
index 00000000..a0e9cb9d
--- /dev/null
+++ b/opendc-simulator/opendc-simulator-flow/src/main/java/org/opendc/simulator/flow2/source/SimpleFlowSource.java
@@ -0,0 +1,131 @@
+/*
+ * Copyright (c) 2022 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.flow2.source;
+
+import java.util.function.Consumer;
+import org.opendc.simulator.flow2.FlowGraph;
+import org.opendc.simulator.flow2.FlowStage;
+import org.opendc.simulator.flow2.FlowStageLogic;
+import org.opendc.simulator.flow2.OutHandler;
+import org.opendc.simulator.flow2.OutPort;
+import org.opendc.simulator.flow2.Outlet;
+
+/**
+ * A flow source that contains a fixed amount and is pushed with a given utilization.
+ */
+public final class SimpleFlowSource implements FlowSource, FlowStageLogic {
+ private final float utilization;
+ private float remainingAmount;
+ private long lastPull;
+
+ private final FlowStage stage;
+ private final OutPort output;
+ private final Consumer<SimpleFlowSource> completionHandler;
+
+ /**
+ * Construct a new {@link SimpleFlowSource}.
+ *
+ * @param graph The {@link FlowGraph} to which this source belongs.
+ * @param amount The amount to transfer via the outlet.
+ * @param utilization The utilization of the capacity of the outlet.
+ * @param completionHandler A callback invoked when the source completes.
+ */
+ public SimpleFlowSource(
+ FlowGraph graph, float amount, float utilization, Consumer<SimpleFlowSource> completionHandler) {
+ if (amount < 0.0) {
+ throw new IllegalArgumentException("Amount must be non-negative");
+ }
+
+ if (utilization <= 0.0) {
+ throw new IllegalArgumentException("Utilization must be positive and non-zero");
+ }
+
+ this.stage = graph.newStage(this);
+ this.output = stage.getOutlet("out");
+ this.output.setHandler(new OutHandler() {
+ @Override
+ public void onPull(OutPort port, float capacity) {}
+
+ @Override
+ public void onDownstreamFinish(OutPort port, Throwable cause) {
+ // Source cannot complete without re-connecting to another sink, so mark the source as completed
+ completionHandler.accept(SimpleFlowSource.this);
+ }
+ });
+ this.completionHandler = completionHandler;
+ this.utilization = utilization;
+ this.remainingAmount = amount;
+ this.lastPull = graph.getEngine().getClock().millis();
+ }
+
+ /**
+ * Construct a new {@link SimpleFlowSource}.
+ *
+ * @param graph The {@link FlowGraph} to which this source belongs.
+ * @param amount The amount to transfer via the outlet.
+ * @param utilization The utilization of the capacity of the outlet.
+ */
+ public SimpleFlowSource(FlowGraph graph, float amount, float utilization) {
+ this(graph, amount, utilization, SimpleFlowSource::close);
+ }
+
+ /**
+ * Return the {@link Outlet} of the source.
+ */
+ @Override
+ public Outlet getOutput() {
+ return output;
+ }
+
+ /**
+ * Remove this node from the graph.
+ */
+ public void close() {
+ stage.close();
+ }
+
+ @Override
+ public long onUpdate(FlowStage ctx, long now) {
+ long lastPull = this.lastPull;
+ this.lastPull = now;
+
+ long delta = Math.max(0, now - lastPull);
+
+ OutPort output = this.output;
+ float consumed = output.getRate() * delta / 1000.f;
+ float limit = output.getCapacity() * utilization;
+
+ float remainingAmount = this.remainingAmount - consumed;
+ this.remainingAmount = remainingAmount;
+
+ long duration = (long) Math.ceil(remainingAmount / limit * 1000);
+
+ if (duration <= 0) {
+ completionHandler.accept(this);
+ return Long.MAX_VALUE;
+ }
+
+ output.push(limit);
+ return now + duration;
+ }
+}
diff --git a/opendc-simulator/opendc-simulator-flow/src/main/java/org/opendc/simulator/flow2/source/TraceFlowSource.java b/opendc-simulator/opendc-simulator-flow/src/main/java/org/opendc/simulator/flow2/source/TraceFlowSource.java
new file mode 100644
index 00000000..e8abc2d7
--- /dev/null
+++ b/opendc-simulator/opendc-simulator-flow/src/main/java/org/opendc/simulator/flow2/source/TraceFlowSource.java
@@ -0,0 +1,151 @@
+/*
+ * Copyright (c) 2022 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.flow2.source;
+
+import java.util.function.Consumer;
+import org.opendc.simulator.flow2.FlowGraph;
+import org.opendc.simulator.flow2.FlowStage;
+import org.opendc.simulator.flow2.FlowStageLogic;
+import org.opendc.simulator.flow2.OutHandler;
+import org.opendc.simulator.flow2.OutPort;
+import org.opendc.simulator.flow2.Outlet;
+
+/**
+ * A flow source that replays a sequence of fragments, each indicating the flow rate for some period of time.
+ */
+public final class TraceFlowSource implements FlowSource, FlowStageLogic {
+ private final OutPort output;
+ private final long[] deadlines;
+ private final float[] usages;
+ private final int size;
+ private int index;
+
+ private final FlowStage stage;
+ private final Consumer<TraceFlowSource> completionHandler;
+
+ /**
+ * Construct a {@link TraceFlowSource}.
+ *
+ * @param graph The {@link FlowGraph} to which the source belongs.
+ * @param trace The {@link Trace} to replay.
+ * @param completionHandler The completion handler to invoke when the source finishes.
+ */
+ public TraceFlowSource(FlowGraph graph, Trace trace, Consumer<TraceFlowSource> completionHandler) {
+ this.stage = graph.newStage(this);
+ this.output = stage.getOutlet("out");
+ this.output.setHandler(new OutHandler() {
+ @Override
+ public void onPull(OutPort port, float capacity) {}
+
+ @Override
+ public void onDownstreamFinish(OutPort port, Throwable cause) {
+ // Source cannot complete without re-connecting to another sink, so mark the source as completed
+ completionHandler.accept(TraceFlowSource.this);
+ }
+ });
+ this.deadlines = trace.deadlines;
+ this.usages = trace.usages;
+ this.size = trace.size;
+ this.completionHandler = completionHandler;
+ }
+
+ /**
+ * Construct a {@link TraceFlowSource}.
+ *
+ * @param graph The {@link FlowGraph} to which the source belongs.
+ * @param trace The {@link Trace} to replay.
+ */
+ public TraceFlowSource(FlowGraph graph, Trace trace) {
+ this(graph, trace, TraceFlowSource::close);
+ }
+
+ @Override
+ public Outlet getOutput() {
+ return output;
+ }
+
+ /**
+ * Remove this node from the graph.
+ */
+ public void close() {
+ stage.close();
+ }
+
+ @Override
+ public long onUpdate(FlowStage ctx, long now) {
+ int size = this.size;
+ int index = this.index;
+ long[] deadlines = this.deadlines;
+ long deadline;
+
+ do {
+ deadline = deadlines[index];
+ } while (deadline <= now && ++index < size);
+
+ if (index >= size) {
+ output.push(0.0f);
+ completionHandler.accept(this);
+ return Long.MAX_VALUE;
+ }
+
+ this.index = index;
+ float usage = usages[index];
+ output.push(usage);
+
+ return deadline;
+ }
+
+ /**
+ * A trace describes the workload over time.
+ */
+ public static final class Trace {
+ private final long[] deadlines;
+ private final float[] usages;
+ private final int size;
+
+ /**
+ * Construct a {@link Trace}.
+ *
+ * @param deadlines The deadlines of the trace fragments.
+ * @param usages The usages of the trace fragments.
+ * @param size The size of the trace.
+ */
+ public Trace(long[] deadlines, float[] usages, int size) {
+ this.deadlines = deadlines;
+ this.usages = usages;
+ this.size = size;
+ }
+
+ public long[] getDeadlines() {
+ return deadlines;
+ }
+
+ public float[] getUsages() {
+ return usages;
+ }
+
+ public int getSize() {
+ return size;
+ }
+ }
+}
diff --git a/opendc-simulator/opendc-simulator-compute/src/main/kotlin/org/opendc/simulator/compute/SimProcessingUnit.kt b/opendc-simulator/opendc-simulator-flow/src/main/java/org/opendc/simulator/flow2/util/FlowTransform.java
index c9f36ece..51ea7df3 100644
--- a/opendc-simulator/opendc-simulator-compute/src/main/kotlin/org/opendc/simulator/compute/SimProcessingUnit.kt
+++ b/opendc-simulator/opendc-simulator-flow/src/main/java/org/opendc/simulator/flow2/util/FlowTransform.java
@@ -1,5 +1,5 @@
/*
- * Copyright (c) 2021 AtLarge Research
+ * Copyright (c) 2022 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,22 +20,22 @@
* SOFTWARE.
*/
-package org.opendc.simulator.compute
+package org.opendc.simulator.flow2.util;
-import org.opendc.simulator.compute.model.ProcessingUnit
-import org.opendc.simulator.flow.FlowConsumer
+import org.opendc.simulator.flow2.FlowGraph;
/**
- * A simulated processing unit.
+ * A {@link FlowTransform} describes a transformation between two components in a {@link FlowGraph} that might operate
+ * at different units of flow.
*/
-public interface SimProcessingUnit : FlowConsumer {
+public interface FlowTransform {
/**
- * The capacity of the processing unit, which can be adjusted by the workload if supported by the machine.
+ * Apply the transform to the specified flow rate.
*/
- public override var capacity: Double
+ float apply(float value);
/**
- * The model representing the static properties of the processing unit.
+ * Apply the inverse of the transformation to the specified flow rate.
*/
- public val model: ProcessingUnit
+ float applyInverse(float value);
}
diff --git a/opendc-simulator/opendc-simulator-flow/src/main/java/org/opendc/simulator/flow2/util/FlowTransformer.java b/opendc-simulator/opendc-simulator-flow/src/main/java/org/opendc/simulator/flow2/util/FlowTransformer.java
new file mode 100644
index 00000000..852240d8
--- /dev/null
+++ b/opendc-simulator/opendc-simulator-flow/src/main/java/org/opendc/simulator/flow2/util/FlowTransformer.java
@@ -0,0 +1,124 @@
+/*
+ * Copyright (c) 2022 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.flow2.util;
+
+import org.opendc.simulator.flow2.*;
+import org.opendc.simulator.flow2.sink.FlowSink;
+import org.opendc.simulator.flow2.source.FlowSource;
+
+/**
+ * Helper class to transform flow from outlet to inlet.
+ */
+public final class FlowTransformer implements FlowStageLogic, FlowSource, FlowSink {
+ private final FlowStage stage;
+ private final InPort input;
+ private final OutPort output;
+
+ /**
+ * Construct a new {@link FlowTransformer}.
+ */
+ public FlowTransformer(FlowGraph graph, FlowTransform transform) {
+ this.stage = graph.newStage(this);
+ this.input = stage.getInlet("in");
+ this.output = stage.getOutlet("out");
+
+ this.input.setHandler(new ForwardInHandler(output, transform));
+ this.input.setMask(true);
+ this.output.setHandler(new ForwardOutHandler(input, transform));
+ this.output.setMask(true);
+ }
+
+ /**
+ * Return the {@link Outlet} of the transformer.
+ */
+ @Override
+ public Outlet getOutput() {
+ return output;
+ }
+
+ /**
+ * Return the {@link Inlet} of the transformer.
+ */
+ @Override
+ public Inlet getInput() {
+ return input;
+ }
+
+ /**
+ * Close the transformer.
+ */
+ void close() {
+ stage.close();
+ }
+
+ @Override
+ public long onUpdate(FlowStage ctx, long now) {
+ return Long.MAX_VALUE;
+ }
+
+ private static class ForwardInHandler implements InHandler {
+ private final OutPort output;
+ private final FlowTransform transform;
+
+ ForwardInHandler(OutPort output, FlowTransform transform) {
+ this.output = output;
+ this.transform = transform;
+ }
+
+ @Override
+ public float getRate(InPort port) {
+ return transform.applyInverse(output.getRate());
+ }
+
+ @Override
+ public void onPush(InPort port, float demand) {
+ float rate = transform.apply(demand);
+ output.push(rate);
+ }
+
+ @Override
+ public void onUpstreamFinish(InPort port, Throwable cause) {
+ output.fail(cause);
+ }
+ }
+
+ private static class ForwardOutHandler implements OutHandler {
+ private final InPort input;
+ private final FlowTransform transform;
+
+ ForwardOutHandler(InPort input, FlowTransform transform) {
+ this.input = input;
+ this.transform = transform;
+ }
+
+ @Override
+ public void onPull(OutPort port, float capacity) {
+ input.pull(transform.applyInverse(capacity));
+ }
+
+ @Override
+ public void onDownstreamFinish(OutPort port, Throwable cause) {
+ input.cancel(cause);
+ }
+ }
+}
diff --git a/opendc-simulator/opendc-simulator-flow/src/main/kotlin/org/opendc/simulator/flow/source/FlowSourceBarrier.kt b/opendc-simulator/opendc-simulator-flow/src/main/java/org/opendc/simulator/flow2/util/FlowTransforms.java
index b3191ad3..428dbfca 100644
--- a/opendc-simulator/opendc-simulator-flow/src/main/kotlin/org/opendc/simulator/flow/source/FlowSourceBarrier.kt
+++ b/opendc-simulator/opendc-simulator-flow/src/main/java/org/opendc/simulator/flow2/util/FlowTransforms.java
@@ -1,5 +1,5 @@
/*
- * Copyright (c) 2021 AtLarge Research
+ * Copyright (c) 2022 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,33 +20,38 @@
* SOFTWARE.
*/
-package org.opendc.simulator.flow.source
+package org.opendc.simulator.flow2.util;
/**
- * 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.
+ * A collection of common {@link FlowTransform} implementations.
*/
-public class FlowSourceBarrier(public val parties: Int) {
- private var counter = 0
+public class FlowTransforms {
+ /**
+ * Prevent construction of this class.
+ */
+ private FlowTransforms() {}
/**
- * Enter the barrier and determine whether the caller is the last to reach the barrier.
- *
- * @return `true` if the caller is the last to reach the barrier, `false` otherwise.
+ * Return a {@link FlowTransform} that forwards the flow rate unmodified.
*/
- public fun enter(): Boolean {
- val last = ++counter == parties
- if (last) {
- counter = 0
- return true
- }
- return false
+ public static FlowTransform noop() {
+ return NoopFlowTransform.INSTANCE;
}
/**
- * Reset the barrier.
+ * No-op implementation of a {@link FlowTransform}.
*/
- public fun reset() {
- counter = 0
+ private static final class NoopFlowTransform implements FlowTransform {
+ static final NoopFlowTransform INSTANCE = new NoopFlowTransform();
+
+ @Override
+ public float apply(float value) {
+ return value;
+ }
+
+ @Override
+ public float applyInverse(float value) {
+ return value;
+ }
}
}
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
deleted file mode 100644
index a49826f4..00000000
--- a/opendc-simulator/opendc-simulator-flow/src/main/kotlin/org/opendc/simulator/flow/FlowConsumer.kt
+++ /dev/null
@@ -1,131 +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.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) {
- try {
- source.onStop(conn, now)
-
- if (!cont.isCompleted) {
- cont.resume(Unit)
- }
- } catch (cause: Throwable) {
- cont.resumeWithException(cause)
- throw cause
- }
- }
-
- override fun onPull(conn: FlowConnection, now: Long): Long {
- return try {
- source.onPull(conn, now)
- } catch (cause: Throwable) {
- cont.resumeWithException(cause)
- throw cause
- }
- }
-
- override fun onConverge(conn: FlowConnection, now: Long) {
- try {
- source.onConverge(conn, now)
- } 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/FlowConsumerLogic.kt b/opendc-simulator/opendc-simulator-flow/src/main/kotlin/org/opendc/simulator/flow/FlowConsumerLogic.kt
deleted file mode 100644
index 1d3adb10..00000000
--- a/opendc-simulator/opendc-simulator-flow/src/main/kotlin/org/opendc/simulator/flow/FlowConsumerLogic.kt
+++ /dev/null
@@ -1,57 +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.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 rate The requested processing rate of the source.
- */
- public fun onPush(ctx: FlowConsumerContext, now: 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.
- */
- public fun onConverge(ctx: FlowConsumerContext, now: 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 cause The cause of the failure or `null` if the source completed.
- */
- public fun onFinish(ctx: FlowConsumerContext, now: Long, cause: Throwable?) {}
-}
diff --git a/opendc-simulator/opendc-simulator-flow/src/main/kotlin/org/opendc/simulator/flow/FlowCounters.kt b/opendc-simulator/opendc-simulator-flow/src/main/kotlin/org/opendc/simulator/flow/FlowCounters.kt
deleted file mode 100644
index d8ad7978..00000000
--- a/opendc-simulator/opendc-simulator-flow/src/main/kotlin/org/opendc/simulator/flow/FlowCounters.kt
+++ /dev/null
@@ -1,48 +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.flow
-
-/**
- * An interface that tracks cumulative counts of the flow accumulation over a stage.
- */
-public interface FlowCounters {
- /**
- * The accumulated flow that a source wanted to push over the connection.
- */
- public val demand: Double
-
- /**
- * The accumulated flow that was actually transferred over the connection.
- */
- public val actual: Double
-
- /**
- * The amount of capacity that was not utilized.
- */
- public val remaining: 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
deleted file mode 100644
index 65224827..00000000
--- a/opendc-simulator/opendc-simulator-flow/src/main/kotlin/org/opendc/simulator/flow/FlowEngine.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.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
deleted file mode 100644
index 5202c252..00000000
--- a/opendc-simulator/opendc-simulator-flow/src/main/kotlin/org/opendc/simulator/flow/FlowForwarder.kt
+++ /dev/null
@@ -1,264 +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.flow
-
-import mu.KotlinLogging
-import org.opendc.simulator.flow.internal.D_MS_TO_S
-import org.opendc.simulator.flow.internal.MutableFlowCounters
-
-/**
- * The logging instance of this connection.
- */
-private val logger = KotlinLogging.logger {}
-
-/**
- * A class that acts as a [FlowSource] and [FlowConsumer] at the same time.
- *
- * @param engine The [FlowEngine] the forwarder runs in.
- * @param listener The convergence lister to use.
- * @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 listener: FlowConvergenceListener? = null,
- private val isCoupled: Boolean = false
-) : FlowSource, FlowConsumer, AutoCloseable {
- /**
- * 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)
- }
-
- 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()
- delegate.onStop(this, now)
- }
- }
- }
-
- /**
- * 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 (listener != null || _ctx.shouldSourceConverge) {
- conn.shouldSourceConverge = true
- }
- }
-
- override fun onStop(conn: FlowConnection, now: Long) {
- _innerCtx = null
-
- val delegate = delegate
- if (delegate != null) {
- reset()
-
- try {
- delegate.onStop(this._ctx, now)
- } catch (cause: Throwable) {
- logger.error(cause) { "Uncaught exception" }
- }
- }
- }
-
- override fun onPull(conn: FlowConnection, now: Long): Long {
- val delegate = delegate
-
- if (!hasDelegateStarted) {
- start()
- }
-
- updateCounters(conn, now)
-
- return try {
- delegate?.onPull(_ctx, now) ?: Long.MAX_VALUE
- } catch (cause: Throwable) {
- logger.error(cause) { "Uncaught exception" }
-
- reset()
- Long.MAX_VALUE
- }
- }
-
- override fun onConverge(conn: FlowConnection, now: Long) {
- try {
- delegate?.onConverge(this._ctx, now)
- listener?.onConverge(now)
- } catch (cause: Throwable) {
- logger.error(cause) { "Uncaught exception" }
-
- _innerCtx = null
- reset()
- }
- }
-
- /**
- * Start the delegate.
- */
- private fun start() {
- val delegate = delegate ?: return
-
- try {
- val now = engine.clock.millis()
- delegate.onStart(_ctx, now)
- hasDelegateStarted = true
- _lastUpdate = now
- } 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
- private var _lastUpdate = 0L
-
- /**
- * Update the flow counters for the transformer.
- */
- private fun updateCounters(ctx: FlowConnection, now: Long) {
- val lastUpdate = _lastUpdate
- _lastUpdate = now
- val delta = now - lastUpdate
- 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))
- }
-}
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
deleted file mode 100644
index af702701..00000000
--- a/opendc-simulator/opendc-simulator-flow/src/main/kotlin/org/opendc/simulator/flow/FlowMapper.kt
+++ /dev/null
@@ -1,75 +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.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) {
- val delegate = checkNotNull(_conn) { "Invariant violation" }
- _conn = null
- source.onStop(delegate, now)
- }
-
- override fun onPull(conn: FlowConnection, now: Long): Long {
- val delegate = checkNotNull(_conn) { "Invariant violation" }
- return source.onPull(delegate, now)
- }
-
- override fun onConverge(conn: FlowConnection, now: Long) {
- val delegate = _conn ?: return
- source.onConverge(delegate, now)
- }
-
- /**
- * 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
deleted file mode 100644
index ee8cd739..00000000
--- a/opendc-simulator/opendc-simulator-flow/src/main/kotlin/org/opendc/simulator/flow/FlowSink.kt
+++ /dev/null
@@ -1,160 +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.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,
- rate: Double
- ) {
- updateCounters(ctx, now, rate, ctx.capacity)
- }
-
- override fun onFinish(ctx: FlowConsumerContext, now: Long, cause: Throwable?) {
- updateCounters(ctx, now, 0.0, 0.0)
-
- _ctx = null
- }
-
- override fun onConverge(ctx: FlowConsumerContext, now: Long) {
- parent?.onConverge(now)
- }
-
- /**
- * The previous demand and capacity for the consumer.
- */
- private val _previous = DoubleArray(2)
- private var _previousUpdate = Long.MAX_VALUE
-
- /**
- * Update the counters of the flow consumer.
- */
- private fun updateCounters(ctx: FlowConnection, now: Long, nextDemand: Double, nextCapacity: Double) {
- val previousUpdate = _previousUpdate
- _previousUpdate = now
- val delta = now - previousUpdate
-
- 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))
- }
- }
-}
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
deleted file mode 100644
index a48ac18e..00000000
--- a/opendc-simulator/opendc-simulator-flow/src/main/kotlin/org/opendc/simulator/flow/FlowSource.kt
+++ /dev/null
@@ -1,67 +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.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.
- */
- public fun onStop(conn: FlowConnection, now: 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.
- * @return The duration after which the resource consumer should be pulled again.
- */
- public fun onPull(conn: FlowConnection, now: 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.
- */
- public fun onConverge(conn: FlowConnection, now: Long) {}
-}
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
deleted file mode 100644
index 97d56fff..00000000
--- a/opendc-simulator/opendc-simulator-flow/src/main/kotlin/org/opendc/simulator/flow/internal/Flags.kt
+++ /dev/null
@@ -1,44 +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.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
deleted file mode 100644
index fba3af5f..00000000
--- a/opendc-simulator/opendc-simulator-flow/src/main/kotlin/org/opendc/simulator/flow/internal/FlowConsumerContextImpl.kt
+++ /dev/null
@@ -1,436 +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.flow.internal
-
-import mu.KotlinLogging
-import org.opendc.simulator.flow.FlowConsumerContext
-import org.opendc.simulator.flow.FlowConsumerLogic
-import org.opendc.simulator.flow.FlowSource
-import org.opendc.simulator.flow.batch
-import java.util.ArrayDeque
-import kotlin.math.min
-
-/**
- * The logging instance of this connection.
- */
-private val logger = KotlinLogging.logger {}
-
-/**
- * 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 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 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) {
- // Update state before calling into the outside world, so it observes a consistent state
- _flags = (flags and ConnPulled.inv()) or ConnUpdateActive
- hasUpdated = true
-
- val duration = source.onPull(this, now)
-
- // 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) {
- // Update state before calling into the outside world, so it observes a consistent state
- _flags = (flags and ConnPushed.inv()) or ConnUpdateActive
- hasUpdated = true
-
- logic.onPush(this, now, _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) {
- source.onConverge(this, now)
- }
-
- // Call the consumer callback if it has enabled convergence
- if (flags and ConnConvergeConsumer != 0) {
- logic.onConverge(this, now)
- }
- } 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)
- doFinishConsumer(now, null)
- } catch (cause: Throwable) {
- doFinishConsumer(now, cause)
- }
- }
-
- /**
- * Fail the [FlowSource].
- */
- private fun doFailSource(now: Long, cause: Throwable) {
- try {
- source.onStop(this, now)
- } catch (e: Throwable) {
- e.addSuppressed(cause)
- doFinishConsumer(now, e)
- }
- }
-
- /**
- * Finish the consumer.
- */
- private fun doFinishConsumer(now: Long, cause: Throwable?) {
- try {
- logic.onFinish(this, now, 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
deleted file mode 100644
index 403a9aec..00000000
--- a/opendc-simulator/opendc-simulator-flow/src/main/kotlin/org/opendc/simulator/flow/internal/FlowDeque.kt
+++ /dev/null
@@ -1,119 +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.flow.internal
-
-import java.util.ArrayDeque
-
-/**
- * A specialized [ArrayDeque] that tracks the [FlowConsumerContextImpl] instances that have updated in an interpreter
- * cycle.
- *
- * By using a specialized class, we reduce the overhead caused by type-erasure.
- */
-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, n)
- _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
deleted file mode 100644
index 6fd1ef31..00000000
--- a/opendc-simulator/opendc-simulator-flow/src/main/kotlin/org/opendc/simulator/flow/internal/FlowEngineImpl.kt
+++ /dev/null
@@ -1,218 +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.flow.internal
-
-import kotlinx.coroutines.Delay
-import kotlinx.coroutines.DisposableHandle
-import kotlinx.coroutines.InternalCoroutinesApi
-import kotlinx.coroutines.Runnable
-import org.opendc.simulator.flow.FlowConsumerContext
-import org.opendc.simulator.flow.FlowConsumerLogic
-import org.opendc.simulator.flow.FlowEngine
-import org.opendc.simulator.flow.FlowSource
-import java.time.Clock
-import java.util.ArrayDeque
-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
deleted file mode 100644
index 47061a91..00000000
--- a/opendc-simulator/opendc-simulator-flow/src/main/kotlin/org/opendc/simulator/flow/internal/FlowTimerQueue.kt
+++ /dev/null
@@ -1,200 +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.flow.internal
-
-/**
- * Specialized priority queue for flow timers.
- *
- * By using a specialized priority queue, we reduce the overhead caused by the default priority queue implementation
- * being generic.
- */
-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
- var deadlines = _deadlines
- if (i >= deadlines.size) {
- grow()
- // Re-fetch the resized array
- deadlines = _deadlines
- }
-
- 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
deleted file mode 100644
index c320a362..00000000
--- a/opendc-simulator/opendc-simulator-flow/src/main/kotlin/org/opendc/simulator/flow/internal/MutableFlowCounters.kt
+++ /dev/null
@@ -1,53 +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.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]
- private val _counters = DoubleArray(3)
-
- override fun reset() {
- _counters.fill(0.0)
- }
-
- public fun increment(demand: Double, actual: Double, remaining: Double) {
- val counters = _counters
- counters[0] += demand
- counters[1] += actual
- counters[2] += remaining
- }
-
- override fun toString(): String {
- return "FlowCounters[demand=$demand,actual=$actual,remaining=$remaining]"
- }
-}
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
deleted file mode 100644
index 8752c559..00000000
--- a/opendc-simulator/opendc-simulator-flow/src/main/kotlin/org/opendc/simulator/flow/mux/FlowMultiplexer.kt
+++ /dev/null
@@ -1,124 +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.flow.mux
-
-import org.opendc.simulator.flow.FlowConsumer
-import org.opendc.simulator.flow.FlowCounters
-import org.opendc.simulator.flow.FlowSource
-
-/**
- * A [FlowMultiplexer] enables multiplexing multiple [FlowSource]s over possibly multiple [FlowConsumer]s.
- */
-public interface FlowMultiplexer {
- /**
- * The maximum number of inputs supported by the multiplexer.
- */
- public val maxInputs: Int
-
- /**
- * The maximum number of outputs supported by the multiplexer.
- */
- public val maxOutputs: Int
-
- /**
- * 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 with a coupled capacity.
- */
- public fun newInput(): FlowConsumer
-
- /**
- * Create a new input on this multiplexer with the specified [capacity].
- *
- * @param capacity The capacity of the input.
- */
- public fun newInput(capacity: Double): 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()
-
- /**
- * Flush the counters of the multiplexer.
- */
- public fun flushCounters()
-
- /**
- * Flush the counters of the specified [input].
- */
- public fun flushCounters(input: FlowConsumer)
-}
diff --git a/opendc-simulator/opendc-simulator-flow/src/main/kotlin/org/opendc/simulator/flow/mux/FlowMultiplexerFactory.kt b/opendc-simulator/opendc-simulator-flow/src/main/kotlin/org/opendc/simulator/flow/mux/FlowMultiplexerFactory.kt
deleted file mode 100644
index a863e3ad..00000000
--- a/opendc-simulator/opendc-simulator-flow/src/main/kotlin/org/opendc/simulator/flow/mux/FlowMultiplexerFactory.kt
+++ /dev/null
@@ -1,60 +0,0 @@
-/*
- * Copyright (c) 2022 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.FlowConvergenceListener
-import org.opendc.simulator.flow.FlowEngine
-
-/**
- * Factory interface for a [FlowMultiplexer] implementation.
- */
-public fun interface FlowMultiplexerFactory {
- /**
- * Construct a new [FlowMultiplexer] using the specified [engine] and [listener].
- */
- public fun newMultiplexer(engine: FlowEngine, listener: FlowConvergenceListener?): FlowMultiplexer
-
- public companion object {
- /**
- * A [FlowMultiplexerFactory] constructing a [MaxMinFlowMultiplexer].
- */
- private val MAX_MIN_FACTORY = FlowMultiplexerFactory { engine, listener -> MaxMinFlowMultiplexer(engine, listener) }
-
- /**
- * A [FlowMultiplexerFactory] constructing a [ForwardingFlowMultiplexer].
- */
- private val FORWARDING_FACTORY = FlowMultiplexerFactory { engine, listener -> ForwardingFlowMultiplexer(engine, listener) }
-
- /**
- * Return a [FlowMultiplexerFactory] that returns [MaxMinFlowMultiplexer] instances.
- */
- @JvmStatic
- public fun maxMinMultiplexer(): FlowMultiplexerFactory = MAX_MIN_FACTORY
-
- /**
- * Return a [ForwardingFlowMultiplexer] that returns [ForwardingFlowMultiplexer] instances.
- */
- @JvmStatic
- public fun forwardingMultiplexer(): FlowMultiplexerFactory = FORWARDING_FACTORY
- }
-}
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
deleted file mode 100644
index 53f94a94..00000000
--- a/opendc-simulator/opendc-simulator-flow/src/main/kotlin/org/opendc/simulator/flow/mux/ForwardingFlowMultiplexer.kt
+++ /dev/null
@@ -1,177 +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.flow.mux
-
-import org.opendc.simulator.flow.FlowConnection
-import org.opendc.simulator.flow.FlowConsumer
-import org.opendc.simulator.flow.FlowConvergenceListener
-import org.opendc.simulator.flow.FlowCounters
-import org.opendc.simulator.flow.FlowEngine
-import org.opendc.simulator.flow.FlowForwarder
-import org.opendc.simulator.flow.FlowSource
-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.
- * @param listener The convergence listener of the multiplexer.
- */
-public class ForwardingFlowMultiplexer(
- private val engine: FlowEngine,
- private val listener: FlowConvergenceListener? = null
-) : FlowMultiplexer, FlowConvergenceListener {
-
- override val maxInputs: Int
- get() = _outputs.size
-
- override val maxOutputs: Int = Int.MAX_VALUE
-
- 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 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(): FlowConsumer {
- val output = checkNotNull(_availableOutputs.poll()) { "No capacity to serve request" }
- val input = Input(output)
- _inputs += input
- return input
- }
-
- override fun newInput(capacity: Double): FlowConsumer = newInput()
-
- 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, this)
- 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()
- }
-
- override fun flushCounters() {}
-
- override fun flushCounters(input: FlowConsumer) {}
-
- override fun onConverge(now: Long) {
- listener?.onConverge(now)
- }
-
- /**
- * 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) {
- forwarder.cancel()
- forwarder.onStop(conn, now)
- }
-
- 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
deleted file mode 100644
index d9c6f893..00000000
--- a/opendc-simulator/opendc-simulator-flow/src/main/kotlin/org/opendc/simulator/flow/mux/MaxMinFlowMultiplexer.kt
+++ /dev/null
@@ -1,811 +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.flow.mux
-
-import org.opendc.simulator.flow.FlowConnection
-import org.opendc.simulator.flow.FlowConsumer
-import org.opendc.simulator.flow.FlowConsumerContext
-import org.opendc.simulator.flow.FlowConsumerLogic
-import org.opendc.simulator.flow.FlowConvergenceListener
-import org.opendc.simulator.flow.FlowCounters
-import org.opendc.simulator.flow.FlowEngine
-import org.opendc.simulator.flow.FlowSource
-import org.opendc.simulator.flow.internal.D_MS_TO_S
-import org.opendc.simulator.flow.internal.MutableFlowCounters
-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.
- */
-public class MaxMinFlowMultiplexer(
- private val engine: FlowEngine,
- parent: FlowConvergenceListener? = null
-) : FlowMultiplexer {
-
- override val maxInputs: Int = Int.MAX_VALUE
-
- override val maxOutputs: Int = Int.MAX_VALUE
-
- /**
- * 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(): FlowConsumer {
- return newInput(isCoupled = true, Double.POSITIVE_INFINITY)
- }
-
- override fun newInput(capacity: Double): FlowConsumer {
- return newInput(isCoupled = false, capacity)
- }
-
- private fun newInput(isCoupled: Boolean, initialCapacity: Double): FlowConsumer {
- val provider = Input(engine, scheduler, isCoupled, initialCapacity)
- _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()
- }
-
- override fun flushCounters() {
- scheduler.updateCounters(engine.clock.millis())
- }
-
- override fun flushCounters(input: FlowConsumer) {
- (input as Input).doUpdateCounters(engine.clock.millis())
- }
-
- /**
- * 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
-
- /**
- * 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
-
- if (input.isCoupled) {
- 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)
- }
- }
-
- /**
- * 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 parent = parent
-
- if (parent != null) {
- _lastConverge = now
- parent.onConverge(now)
- }
-
- 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 {
- return try {
- _schedulerActive = true
- doRunScheduler(now)
- } 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) {
- if (input.isCoupled) {
- 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): Long {
- val activeInputs = _activeInputs
- val activeOutputs = _activeOutputs
- var inputArray = _inputArray
- var inputSize = _inputArray.size
-
- // Update the counters of the scheduler
- updateCounters(now)
-
- // 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
- private var _previousUpdate = Long.MIN_VALUE
-
- /**
- * Update the counters of the scheduler.
- */
- fun updateCounters(now: Long) {
- val previousCapacity = _previousCapacity
- _previousCapacity = capacity
-
- val previousUpdate = _previousUpdate
- _previousUpdate = now
-
- val delta = now - previousUpdate
- 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
- )
- }
- }
-
- /**
- * An internal [FlowConsumer] implementation for multiplexer inputs.
- */
- private class Input(
- private val engine: FlowEngine,
- private val scheduler: Scheduler,
- @JvmField val isCoupled: Boolean,
- 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,
- rate: Double
- ) {
- doUpdateCounters(now)
-
- val allowed = min(rate, capacity)
- limit = rate
- actualRate = allowed
- allowedRate = allowed
-
- scheduler.trigger(now)
- }
-
- override fun onFinish(ctx: FlowConsumerContext, now: Long, cause: Throwable?) {
- doUpdateCounters(now)
-
- limit = 0.0
- actualRate = 0.0
- allowedRate = 0.0
-
- scheduler.deregisterInput(this, now)
-
- _ctx = null
- }
-
- override fun onConverge(ctx: FlowConsumerContext, now: Long) {
- scheduler.convergeInput(this, now)
- }
-
- /* Comparable */
- override fun compareTo(other: Input): Int = allowedRate.compareTo(other.allowedRate)
-
- /**
- * The timestamp that the counters where last updated.
- */
- private var _lastUpdate = Long.MIN_VALUE
-
- /**
- * Helper method to update the flow counters of the multiplexer.
- */
- fun doUpdateCounters(now: Long) {
- val lastUpdate = _lastUpdate
- _lastUpdate = now
-
- val delta = (now - lastUpdate).coerceAtLeast(0)
- if (delta <= 0L) {
- return
- }
-
- val actualRate = actualRate
-
- val deltaS = delta * D_MS_TO_S
- val demand = limit * deltaS
- val actual = actualRate * deltaS
- val remaining = (_capacity - actualRate) * deltaS
-
- _counters.increment(demand, actual, remaining)
- scheduler.counters.increment(0.0, 0.0, 0.0)
- }
- }
-
- /**
- * 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) {
- _conn = null
- capacity = 0.0
- isActive = false
-
- scheduler.deregisterOutput(this, now)
- }
-
- override fun onPull(conn: FlowConnection, now: 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) {
- if (_isActivationOutput) {
- scheduler.convergeOutput(this, now)
- }
- }
-
- override fun compareTo(other: Output): Int = capacity.compareTo(other.capacity)
- }
-}
diff --git a/opendc-simulator/opendc-simulator-flow/src/main/kotlin/org/opendc/simulator/flow/source/FixedFlowSource.kt b/opendc-simulator/opendc-simulator-flow/src/main/kotlin/org/opendc/simulator/flow/source/FixedFlowSource.kt
deleted file mode 100644
index 6cfcc82c..00000000
--- a/opendc-simulator/opendc-simulator-flow/src/main/kotlin/org/opendc/simulator/flow/source/FixedFlowSource.kt
+++ /dev/null
@@ -1,66 +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.flow.source
-
-import org.opendc.simulator.flow.FlowConnection
-import org.opendc.simulator.flow.FlowSource
-import kotlin.math.roundToLong
-
-/**
- * A [FlowSource] that contains a fixed [amount] and is pushed with a given [utilization].
- */
-public class FixedFlowSource(private val amount: Double, private val utilization: Double) : FlowSource {
-
- init {
- require(amount >= 0.0) { "Amount must be positive" }
- require(utilization > 0.0) { "Utilization must be positive" }
- }
-
- private var remainingAmount = amount
- private var lastPull: Long = 0L
-
- override fun onStart(conn: FlowConnection, now: Long) {
- lastPull = now
- }
-
- override fun onPull(conn: FlowConnection, now: Long): Long {
- val lastPull = lastPull
- this.lastPull = now
- val delta = (now - lastPull).coerceAtLeast(0)
-
- 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 {
- conn.close()
- Long.MAX_VALUE
- }
- }
-}
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
deleted file mode 100644
index 80127fb5..00000000
--- a/opendc-simulator/opendc-simulator-flow/src/main/kotlin/org/opendc/simulator/flow/source/FlowSourceRateAdapter.kt
+++ /dev/null
@@ -1,77 +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.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) {
- try {
- delegate.onStop(conn, now)
- } finally {
- rate = 0.0
- }
- }
-
- override fun onPull(conn: FlowConnection, now: Long): Long {
- return delegate.onPull(conn, now)
- }
-
- override fun onConverge(conn: FlowConnection, now: Long) {
- try {
- delegate.onConverge(conn, now)
- } 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
deleted file mode 100644
index c9a52128..00000000
--- a/opendc-simulator/opendc-simulator-flow/src/main/kotlin/org/opendc/simulator/flow/source/TraceFlowSource.kt
+++ /dev/null
@@ -1,67 +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.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) {
- _iterator = null
- }
-
- override fun onPull(conn: FlowConnection, now: 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
deleted file mode 100644
index f89133dd..00000000
--- a/opendc-simulator/opendc-simulator-flow/src/test/kotlin/org/opendc/simulator/flow/FlowConsumerContextTest.kt
+++ /dev/null
@@ -1,107 +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.flow
-
-import io.mockk.spyk
-import io.mockk.verify
-import net.bytebuddy.matcher.ElementMatchers.any
-import org.junit.jupiter.api.Test
-import org.junit.jupiter.api.assertThrows
-import org.opendc.simulator.flow.internal.FlowConsumerContextImpl
-import org.opendc.simulator.flow.internal.FlowEngineImpl
-import org.opendc.simulator.kotlin.runSimulation
-
-/**
- * A test suite for the [FlowConsumerContextImpl] class.
- */
-class FlowConsumerContextTest {
- @Test
- fun testFlushWithoutCommand() = runSimulation {
- val engine = FlowEngineImpl(coroutineContext, clock)
- val consumer = object : FlowSource {
- override fun onPull(conn: FlowConnection, now: 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() = runSimulation {
- val engine = FlowEngineImpl(coroutineContext, clock)
- val consumer = object : FlowSource {
- override fun onPull(conn: FlowConnection, now: 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() = runSimulation {
- val engine = FlowEngineImpl(coroutineContext, clock)
- val consumer = spyk(object : FlowSource {
- override fun onPull(conn: FlowConnection, now: 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()) }
- }
-}
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
deleted file mode 100644
index f75e5037..00000000
--- a/opendc-simulator/opendc-simulator-flow/src/test/kotlin/org/opendc/simulator/flow/FlowForwarderTest.kt
+++ /dev/null
@@ -1,331 +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.flow
-
-import io.mockk.spyk
-import io.mockk.verify
-import kotlinx.coroutines.coroutineScope
-import kotlinx.coroutines.delay
-import kotlinx.coroutines.launch
-import kotlinx.coroutines.yield
-import net.bytebuddy.matcher.ElementMatchers.any
-import org.junit.jupiter.api.Assertions.assertAll
-import org.junit.jupiter.api.Assertions.assertEquals
-import org.junit.jupiter.api.Assertions.assertFalse
-import org.junit.jupiter.api.Assertions.assertTrue
-import org.junit.jupiter.api.Disabled
-import org.junit.jupiter.api.Test
-import org.junit.jupiter.api.assertThrows
-import org.opendc.simulator.flow.internal.FlowEngineImpl
-import org.opendc.simulator.flow.source.FixedFlowSource
-import org.opendc.simulator.kotlin.runSimulation
-
-/**
- * A test suite for the [FlowForwarder] class.
- */
-internal class FlowForwarderTest {
- @Test
- fun testCancelImmediately() = runSimulation {
- 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): Long {
- conn.close()
- return Long.MAX_VALUE
- }
- })
-
- forwarder.close()
- source.cancel()
- }
-
- @Test
- fun testCancel() = runSimulation {
- 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): 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() = runSimulation {
- val engine = FlowEngineImpl(coroutineContext, clock)
- val forwarder = FlowForwarder(engine)
- val consumer = object : FlowSource {
- override fun onPull(conn: FlowConnection, now: 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() = runSimulation {
- val engine = FlowEngineImpl(coroutineContext, clock)
- val forwarder = FlowForwarder(engine)
-
- val consumer = spyk(object : FlowSource {
- override fun onPull(conn: FlowConnection, now: Long): Long {
- conn.close()
- return Long.MAX_VALUE
- }
- })
-
- forwarder.startConsumer(consumer)
- forwarder.cancel()
-
- verify(exactly = 0) { consumer.onStop(any(), any()) }
- }
-
- @Test
- fun testCancelStartedDelegate() = runSimulation {
- 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()) }
- }
-
- @Test
- fun testCancelPropagation() = runSimulation {
- 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()) }
- }
-
- @Test
- fun testExitPropagation() = runSimulation {
- 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): 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() = runSimulation {
- 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()) }
- }
-
- @Test
- fun testCounters() = runSimulation {
- 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()
-
- assertAll(
- { 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() = runSimulation {
- 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() = runSimulation {
- 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): Long {
- throw IllegalStateException("Test")
- }
- })
- } catch (cause: Throwable) {
- // Ignore
- }
-
- yield()
-
- assertFalse(source.isActive)
- }
-
- @Test
- fun testStartFailure() = runSimulation {
- 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): 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() = runSimulation {
- 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): Long {
- return Long.MAX_VALUE
- }
-
- override fun onConverge(conn: FlowConnection, now: 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
deleted file mode 100644
index 746d752d..00000000
--- a/opendc-simulator/opendc-simulator-flow/src/test/kotlin/org/opendc/simulator/flow/FlowSinkTest.kt
+++ /dev/null
@@ -1,245 +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.flow
-
-import io.mockk.spyk
-import io.mockk.verify
-import kotlinx.coroutines.coroutineScope
-import kotlinx.coroutines.delay
-import kotlinx.coroutines.launch
-import kotlinx.coroutines.yield
-import org.junit.jupiter.api.Assertions.assertEquals
-import org.junit.jupiter.api.Test
-import org.junit.jupiter.api.assertThrows
-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.kotlin.runSimulation
-
-/**
- * A test suite for the [FlowSink] class.
- */
-internal class FlowSinkTest {
- @Test
- fun testSpeed() = runSimulation {
- 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() = runSimulation {
- 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()) }
- }
-
- @Test
- fun testSpeedLimit() = runSimulation {
- 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() = runSimulation {
- 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): Long {
- conn.close()
- return Long.MAX_VALUE
- }
-
- override fun onStart(conn: FlowConnection, now: Long) {
- conn.pull()
- }
- }
-
- provider.consume(consumer)
- }
-
- @Test
- fun testInterrupt() = runSimulation {
- 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): 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() = runSimulation {
- 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): Long {
- return Long.MAX_VALUE
- }
- }
-
- assertThrows<IllegalStateException> {
- provider.consume(consumer)
- }
- }
-
- @Test
- fun testExceptionPropagationOnNext() = runSimulation {
- 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): Long {
- return if (isFirst) {
- isFirst = false
- conn.push(1.0)
- 1000
- } else {
- throw IllegalStateException()
- }
- }
- }
-
- assertThrows<IllegalStateException> {
- provider.consume(consumer)
- }
- }
-
- @Test
- fun testConcurrentConsumption() = runSimulation {
- 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() = runSimulation {
- 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> {
- runSimulation {
- 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): 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
deleted file mode 100644
index 2409e174..00000000
--- a/opendc-simulator/opendc-simulator-flow/src/test/kotlin/org/opendc/simulator/flow/mux/ForwardingFlowMultiplexerTest.kt
+++ /dev/null
@@ -1,158 +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.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.flow.FlowConnection
-import org.opendc.simulator.flow.FlowForwarder
-import org.opendc.simulator.flow.FlowSink
-import org.opendc.simulator.flow.FlowSource
-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.FlowSourceRateAdapter
-import org.opendc.simulator.flow.source.TraceFlowSource
-import org.opendc.simulator.kotlin.runSimulation
-
-/**
- * Test suite for the [ForwardingFlowMultiplexer] class.
- */
-internal class ForwardingFlowMultiplexerTest {
- /**
- * Test a trace workload.
- */
- @Test
- fun testTrace() = runSimulation {
- 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() = runSimulation {
- 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() = runSimulation {
- 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): 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() = runSimulation {
- 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
deleted file mode 100644
index a6bf8ad8..00000000
--- a/opendc-simulator/opendc-simulator-flow/src/test/kotlin/org/opendc/simulator/flow/mux/MaxMinFlowMultiplexerTest.kt
+++ /dev/null
@@ -1,150 +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.flow.mux
-
-import kotlinx.coroutines.coroutineScope
-import kotlinx.coroutines.launch
-import kotlinx.coroutines.yield
-import org.junit.jupiter.api.Assertions.assertAll
-import org.junit.jupiter.api.Assertions.assertEquals
-import org.junit.jupiter.api.Test
-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
-import org.opendc.simulator.kotlin.runSimulation
-
-/**
- * Test suite for the [FlowMultiplexer] implementations
- */
-internal class MaxMinFlowMultiplexerTest {
- @Test
- fun testSmoke() = runSimulation {
- 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() = runSimulation {
- 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() = runSimulation {
- 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-flow/src/test/kotlin/org/opendc/simulator/flow2/FlowEngineTest.kt b/opendc-simulator/opendc-simulator-flow/src/test/kotlin/org/opendc/simulator/flow2/FlowEngineTest.kt
new file mode 100644
index 00000000..839835ce
--- /dev/null
+++ b/opendc-simulator/opendc-simulator-flow/src/test/kotlin/org/opendc/simulator/flow2/FlowEngineTest.kt
@@ -0,0 +1,197 @@
+/*
+ * Copyright (c) 2022 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.flow2
+
+import io.mockk.mockk
+import org.junit.jupiter.api.Assertions.assertEquals
+import org.junit.jupiter.api.Assertions.assertNotEquals
+import org.junit.jupiter.api.Test
+import org.junit.jupiter.api.assertThrows
+import org.opendc.simulator.flow2.mux.MaxMinFlowMultiplexer
+import org.opendc.simulator.flow2.sink.SimpleFlowSink
+import org.opendc.simulator.flow2.source.SimpleFlowSource
+import org.opendc.simulator.kotlin.runSimulation
+
+/**
+ * Smoke tests for the Flow API.
+ */
+class FlowEngineTest {
+ @Test
+ fun testSmoke() = runSimulation {
+ val engine = FlowEngine.create(coroutineContext, clock)
+ val graph = engine.newGraph()
+
+ val multiplexer = MaxMinFlowMultiplexer(graph)
+ val sink = SimpleFlowSink(graph, 2.0f)
+
+ graph.connect(multiplexer.newOutput(), sink.input)
+
+ val sourceA = SimpleFlowSource(graph, 2000.0f, 0.8f)
+ val sourceB = SimpleFlowSource(graph, 2000.0f, 0.8f)
+
+ graph.connect(sourceA.output, multiplexer.newInput())
+ graph.connect(sourceB.output, multiplexer.newInput())
+ }
+
+ @Test
+ fun testConnectInvalidInlet() = runSimulation {
+ val engine = FlowEngine.create(coroutineContext, clock)
+ val graph = engine.newGraph()
+
+ val inlet = mockk<Inlet>()
+ val source = SimpleFlowSource(graph, 2000.0f, 0.8f)
+ assertThrows<IllegalArgumentException> { graph.connect(source.output, inlet) }
+ }
+
+ @Test
+ fun testConnectInvalidOutlet() = runSimulation {
+ val engine = FlowEngine.create(coroutineContext, clock)
+ val graph = engine.newGraph()
+
+ val outlet = mockk<Outlet>()
+ val sink = SimpleFlowSink(graph, 2.0f)
+ assertThrows<IllegalArgumentException> { graph.connect(outlet, sink.input) }
+ }
+
+ @Test
+ fun testConnectInletBelongsToDifferentGraph() = runSimulation {
+ val engine = FlowEngine.create(coroutineContext, clock)
+ val graphA = engine.newGraph()
+ val graphB = engine.newGraph()
+
+ val sink = SimpleFlowSink(graphB, 2.0f)
+ val source = SimpleFlowSource(graphA, 2000.0f, 0.8f)
+
+ assertThrows<IllegalArgumentException> { graphA.connect(source.output, sink.input) }
+ }
+
+ @Test
+ fun testConnectOutletBelongsToDifferentGraph() = runSimulation {
+ val engine = FlowEngine.create(coroutineContext, clock)
+ val graphA = engine.newGraph()
+ val graphB = engine.newGraph()
+
+ val sink = SimpleFlowSink(graphA, 2.0f)
+ val source = SimpleFlowSource(graphB, 2000.0f, 0.8f)
+
+ assertThrows<IllegalArgumentException> { graphA.connect(source.output, sink.input) }
+ }
+
+ @Test
+ fun testConnectInletAlreadyConnected() = runSimulation {
+ val engine = FlowEngine.create(coroutineContext, clock)
+ val graph = engine.newGraph()
+
+ val sink = SimpleFlowSink(graph, 2.0f)
+ val sourceA = SimpleFlowSource(graph, 2000.0f, 0.8f)
+ val sourceB = SimpleFlowSource(graph, 2000.0f, 0.8f)
+
+ graph.connect(sourceA.output, sink.input)
+ assertThrows<IllegalStateException> { graph.connect(sourceB.output, sink.input) }
+ }
+
+ @Test
+ fun testConnectOutletAlreadyConnected() = runSimulation {
+ val engine = FlowEngine.create(coroutineContext, clock)
+ val graph = engine.newGraph()
+
+ val sinkA = SimpleFlowSink(graph, 2.0f)
+ val sinkB = SimpleFlowSink(graph, 2.0f)
+ val source = SimpleFlowSource(graph, 2000.0f, 0.8f)
+
+ graph.connect(source.output, sinkA.input)
+ assertThrows<IllegalStateException> { graph.connect(source.output, sinkB.input) }
+ }
+
+ @Test
+ fun testDisconnectInletInvalid() = runSimulation {
+ val engine = FlowEngine.create(coroutineContext, clock)
+ val graph = engine.newGraph()
+
+ val inlet = mockk<Inlet>()
+ assertThrows<IllegalArgumentException> { graph.disconnect(inlet) }
+ }
+
+ @Test
+ fun testDisconnectOutletInvalid() = runSimulation {
+ val engine = FlowEngine.create(coroutineContext, clock)
+ val graph = engine.newGraph()
+
+ val outlet = mockk<Outlet>()
+ assertThrows<IllegalArgumentException> { graph.disconnect(outlet) }
+ }
+
+ @Test
+ fun testDisconnectInletInvalidGraph() = runSimulation {
+ val engine = FlowEngine.create(coroutineContext, clock)
+ val graphA = engine.newGraph()
+ val graphB = engine.newGraph()
+
+ val sink = SimpleFlowSink(graphA, 2.0f)
+
+ assertThrows<IllegalArgumentException> { graphB.disconnect(sink.input) }
+ }
+
+ @Test
+ fun testDisconnectOutletInvalidGraph() = runSimulation {
+ val engine = FlowEngine.create(coroutineContext, clock)
+ val graphA = engine.newGraph()
+ val graphB = engine.newGraph()
+
+ val source = SimpleFlowSource(graphA, 2000.0f, 0.8f)
+
+ assertThrows<IllegalArgumentException> { graphB.disconnect(source.output) }
+ }
+
+ @Test
+ fun testInletEquality() = runSimulation {
+ val engine = FlowEngine.create(coroutineContext, clock)
+ val graph = engine.newGraph()
+
+ val sinkA = SimpleFlowSink(graph, 2.0f)
+ val sinkB = SimpleFlowSink(graph, 2.0f)
+
+ val multiplexer = MaxMinFlowMultiplexer(graph)
+
+ assertEquals(sinkA.input, sinkA.input)
+ assertNotEquals(sinkA.input, sinkB.input)
+
+ assertNotEquals(multiplexer.newInput(), multiplexer.newInput())
+ }
+
+ @Test
+ fun testOutletEquality() = runSimulation {
+ val engine = FlowEngine.create(coroutineContext, clock)
+ val graph = engine.newGraph()
+
+ val sourceA = SimpleFlowSource(graph, 2000.0f, 0.8f)
+ val sourceB = SimpleFlowSource(graph, 2000.0f, 0.8f)
+
+ val multiplexer = MaxMinFlowMultiplexer(graph)
+
+ assertEquals(sourceA.output, sourceA.output)
+ assertNotEquals(sourceA.output, sourceB.output)
+
+ assertNotEquals(multiplexer.newOutput(), multiplexer.newOutput())
+ }
+}
diff --git a/opendc-simulator/opendc-simulator-flow/src/test/kotlin/org/opendc/simulator/flow2/FlowTimerQueueTest.kt b/opendc-simulator/opendc-simulator-flow/src/test/kotlin/org/opendc/simulator/flow2/FlowTimerQueueTest.kt
new file mode 100644
index 00000000..1824959c
--- /dev/null
+++ b/opendc-simulator/opendc-simulator-flow/src/test/kotlin/org/opendc/simulator/flow2/FlowTimerQueueTest.kt
@@ -0,0 +1,385 @@
+/*
+ * Copyright (c) 2022 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.flow2
+
+import io.mockk.mockk
+import org.junit.jupiter.api.Assertions.assertAll
+import org.junit.jupiter.api.Assertions.assertEquals
+import org.junit.jupiter.api.Assertions.assertNull
+import org.junit.jupiter.api.BeforeEach
+import org.junit.jupiter.api.Test
+
+/**
+ * Test suite for the [FlowTimerQueue] class.
+ */
+class FlowTimerQueueTest {
+ private lateinit var queue: FlowTimerQueue
+
+ @BeforeEach
+ fun setUp() {
+ queue = FlowTimerQueue(3)
+ }
+
+ /**
+ * Test whether a call to [FlowTimerQueue.poll] returns `null` for an empty queue.
+ */
+ @Test
+ fun testPollEmpty() {
+ assertAll(
+ { assertEquals(Long.MAX_VALUE, queue.peekDeadline()) },
+ { assertNull(queue.poll(100L)) }
+ )
+ }
+
+ /**
+ * Test whether a call to [FlowTimerQueue.poll] returns the proper value for a queue with a single entry.
+ */
+ @Test
+ fun testSingleEntry() {
+ val entry = mockk<FlowStage>()
+ entry.deadline = 100
+ entry.timerIndex = -1
+
+ queue.enqueue(entry)
+
+ assertAll(
+ { assertEquals(100, queue.peekDeadline()) },
+ { assertNull(queue.poll(10L)) },
+ { assertEquals(entry, queue.poll(200L)) },
+ { assertNull(queue.poll(200L)) }
+ )
+ }
+
+ /**
+ * Test whether [FlowTimerQueue.poll] returns values in the queue in the proper order.
+ */
+ @Test
+ fun testMultipleEntries() {
+ val entryA = mockk<FlowStage>()
+ entryA.deadline = 100
+ entryA.timerIndex = -1
+
+ queue.enqueue(entryA)
+
+ val entryB = mockk<FlowStage>()
+ entryB.deadline = 10
+ entryB.timerIndex = -1
+
+ queue.enqueue(entryB)
+
+ val entryC = mockk<FlowStage>()
+ entryC.deadline = 58
+ entryC.timerIndex = -1
+
+ queue.enqueue(entryC)
+
+ assertAll(
+ { assertEquals(10, queue.peekDeadline()) },
+ { assertEquals(entryB, queue.poll(100L)) },
+ { assertEquals(entryC, queue.poll(100L)) },
+ { assertEquals(entryA, queue.poll(100L)) },
+ { assertNull(queue.poll(100L)) }
+ )
+ }
+
+ /**
+ * Test that the queue is properly resized when the number of entries exceed the capacity.
+ */
+ @Test
+ fun testResize() {
+ val entryA = mockk<FlowStage>()
+ entryA.deadline = 100
+ entryA.timerIndex = -1
+
+ queue.enqueue(entryA)
+
+ val entryB = mockk<FlowStage>()
+ entryB.deadline = 20
+ entryB.timerIndex = -1
+
+ queue.enqueue(entryB)
+
+ val entryC = mockk<FlowStage>()
+ entryC.deadline = 58
+ entryC.timerIndex = -1
+
+ queue.enqueue(entryC)
+
+ val entryD = mockk<FlowStage>()
+ entryD.deadline = 31
+ entryD.timerIndex = -1
+
+ queue.enqueue(entryD)
+
+ assertAll(
+ { assertEquals(20, queue.peekDeadline()) },
+ { assertEquals(entryB, queue.poll(100L)) },
+ { assertEquals(entryD, queue.poll(100L)) },
+ { assertEquals(entryC, queue.poll(100L)) },
+ { assertEquals(entryA, queue.poll(100L)) },
+ { assertNull(queue.poll(100L)) }
+ )
+ }
+
+ /**
+ * Test to verify that we can change the deadline of the last element in the queue.
+ */
+ @Test
+ fun testChangeDeadlineTail() {
+ val entryA = mockk<FlowStage>()
+ entryA.deadline = 100
+ entryA.timerIndex = -1
+
+ queue.enqueue(entryA)
+
+ val entryB = mockk<FlowStage>()
+ entryB.deadline = 20
+ entryB.timerIndex = -1
+
+ queue.enqueue(entryB)
+
+ val entryC = mockk<FlowStage>()
+ entryC.deadline = 58
+ entryC.timerIndex = -1
+
+ queue.enqueue(entryC)
+
+ entryA.deadline = 10
+ queue.enqueue(entryA)
+
+ assertAll(
+ { assertEquals(10, queue.peekDeadline()) },
+ { assertEquals(entryA, queue.poll(100L)) },
+ { assertEquals(entryB, queue.poll(100L)) },
+ { assertEquals(entryC, queue.poll(100L)) },
+ { assertNull(queue.poll(100L)) }
+ )
+ }
+
+ /**
+ * Test that we can change the deadline of the head entry in the queue.
+ */
+ @Test
+ fun testChangeDeadlineMiddle() {
+ val entryA = mockk<FlowStage>()
+ entryA.deadline = 100
+ entryA.timerIndex = -1
+
+ queue.enqueue(entryA)
+
+ val entryB = mockk<FlowStage>()
+ entryB.deadline = 20
+ entryB.timerIndex = -1
+
+ queue.enqueue(entryB)
+
+ val entryC = mockk<FlowStage>()
+ entryC.deadline = 58
+ entryC.timerIndex = -1
+
+ queue.enqueue(entryC)
+
+ entryC.deadline = 10
+ queue.enqueue(entryC)
+
+ assertAll(
+ { assertEquals(10, queue.peekDeadline()) },
+ { assertEquals(entryC, queue.poll(100L)) },
+ { assertEquals(entryB, queue.poll(100L)) },
+ { assertEquals(entryA, queue.poll(100L)) },
+ { assertNull(queue.poll(100L)) }
+ )
+ }
+
+ /**
+ * Test that we can change the deadline of the head entry in the queue.
+ */
+ @Test
+ fun testChangeDeadlineHead() {
+ val entryA = mockk<FlowStage>()
+ entryA.deadline = 100
+ entryA.timerIndex = -1
+
+ queue.enqueue(entryA)
+
+ val entryB = mockk<FlowStage>()
+ entryB.deadline = 20
+ entryB.timerIndex = -1
+
+ queue.enqueue(entryB)
+
+ val entryC = mockk<FlowStage>()
+ entryC.deadline = 58
+ entryC.timerIndex = -1
+
+ queue.enqueue(entryC)
+
+ entryB.deadline = 30
+ queue.enqueue(entryB)
+
+ assertAll(
+ { assertEquals(30, queue.peekDeadline()) },
+ { assertEquals(entryB, queue.poll(100L)) },
+ { assertEquals(entryC, queue.poll(100L)) },
+ { assertEquals(entryA, queue.poll(100L)) },
+ { assertNull(queue.poll(100L)) }
+ )
+ }
+
+ /**
+ * Test that an unchanged deadline results in a no-op.
+ */
+ @Test
+ fun testChangeDeadlineNop() {
+ val entryA = mockk<FlowStage>()
+ entryA.deadline = 100
+ entryA.timerIndex = -1
+
+ queue.enqueue(entryA)
+
+ val entryB = mockk<FlowStage>()
+ entryB.deadline = 20
+ entryB.timerIndex = -1
+
+ queue.enqueue(entryB)
+
+ val entryC = mockk<FlowStage>()
+ entryC.deadline = 58
+ entryC.timerIndex = -1
+
+ queue.enqueue(entryC)
+
+ // Should be a no-op
+ queue.enqueue(entryA)
+
+ assertAll(
+ { assertEquals(20, queue.peekDeadline()) },
+ { assertEquals(entryB, queue.poll(100L)) },
+ { assertEquals(entryC, queue.poll(100L)) },
+ { assertEquals(entryA, queue.poll(100L)) },
+ { assertNull(queue.poll(100L)) }
+ )
+ }
+
+ /**
+ * Test that we can remove an entry from the end of the queue.
+ */
+ @Test
+ fun testRemoveEntryTail() {
+ val entryA = mockk<FlowStage>()
+ entryA.deadline = 100
+ entryA.timerIndex = -1
+
+ queue.enqueue(entryA)
+
+ val entryB = mockk<FlowStage>()
+ entryB.deadline = 20
+ entryB.timerIndex = -1
+
+ queue.enqueue(entryB)
+
+ val entryC = mockk<FlowStage>()
+ entryC.deadline = 58
+ entryC.timerIndex = -1
+
+ queue.enqueue(entryC)
+
+ entryC.deadline = Long.MAX_VALUE
+ queue.enqueue(entryC)
+
+ assertAll(
+ { assertEquals(20, queue.peekDeadline()) },
+ { assertEquals(entryB, queue.poll(100L)) },
+ { assertEquals(entryA, queue.poll(100L)) },
+ { assertNull(queue.poll(100L)) }
+ )
+ }
+
+ /**
+ * Test that we can remove an entry from the head of the queue.
+ */
+ @Test
+ fun testRemoveEntryHead() {
+ val entryA = mockk<FlowStage>()
+ entryA.deadline = 100
+ entryA.timerIndex = -1
+
+ queue.enqueue(entryA)
+
+ val entryB = mockk<FlowStage>()
+ entryB.deadline = 20
+ entryB.timerIndex = -1
+
+ queue.enqueue(entryB)
+
+ val entryC = mockk<FlowStage>()
+ entryC.deadline = 58
+ entryC.timerIndex = -1
+
+ queue.enqueue(entryC)
+
+ entryB.deadline = Long.MAX_VALUE
+ queue.enqueue(entryB)
+
+ assertAll(
+ { assertEquals(58, queue.peekDeadline()) },
+ { assertEquals(entryC, queue.poll(100L)) },
+ { assertEquals(entryA, queue.poll(100L)) },
+ { assertNull(queue.poll(100L)) }
+ )
+ }
+
+ /**
+ * Test that we can remove an entry from the middle of a queue.
+ */
+ @Test
+ fun testRemoveEntryMiddle() {
+ val entryA = mockk<FlowStage>()
+ entryA.deadline = 100
+ entryA.timerIndex = -1
+
+ queue.enqueue(entryA)
+
+ val entryB = mockk<FlowStage>()
+ entryB.deadline = 20
+ entryB.timerIndex = -1
+
+ queue.enqueue(entryB)
+
+ val entryC = mockk<FlowStage>()
+ entryC.deadline = 58
+ entryC.timerIndex = -1
+
+ queue.enqueue(entryC)
+
+ entryC.deadline = Long.MAX_VALUE
+ queue.enqueue(entryC)
+
+ assertAll(
+ { assertEquals(20, queue.peekDeadline()) },
+ { assertEquals(entryB, queue.poll(100L)) },
+ { assertEquals(entryA, queue.poll(100L)) },
+ { assertNull(queue.poll(100L)) }
+ )
+ }
+}
diff --git a/opendc-simulator/opendc-simulator-flow/src/test/kotlin/org/opendc/simulator/flow2/InvocationStackTest.kt b/opendc-simulator/opendc-simulator-flow/src/test/kotlin/org/opendc/simulator/flow2/InvocationStackTest.kt
new file mode 100644
index 00000000..2250fe87
--- /dev/null
+++ b/opendc-simulator/opendc-simulator-flow/src/test/kotlin/org/opendc/simulator/flow2/InvocationStackTest.kt
@@ -0,0 +1,71 @@
+/*
+ * Copyright (c) 2022 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.flow2
+
+import org.junit.jupiter.api.Assertions.assertEquals
+import org.junit.jupiter.api.Assertions.assertFalse
+import org.junit.jupiter.api.Assertions.assertTrue
+import org.junit.jupiter.api.Test
+
+/**
+ * Test suite for the [InvocationStack] class.
+ */
+class InvocationStackTest {
+ private val stack = InvocationStack(2)
+
+ @Test
+ fun testPollEmpty() {
+ assertEquals(Long.MAX_VALUE, stack.poll())
+ }
+
+ @Test
+ fun testAddSingle() {
+ assertTrue(stack.tryAdd(10))
+ assertEquals(10, stack.poll())
+ }
+
+ @Test
+ fun testAddLater() {
+ assertTrue(stack.tryAdd(10))
+ assertFalse(stack.tryAdd(15))
+ assertEquals(10, stack.poll())
+ }
+
+ @Test
+ fun testAddEarlier() {
+ assertTrue(stack.tryAdd(10))
+ assertTrue(stack.tryAdd(5))
+ assertEquals(5, stack.poll())
+ assertEquals(10, stack.poll())
+ }
+
+ @Test
+ fun testCapacityExceeded() {
+ assertTrue(stack.tryAdd(10))
+ assertTrue(stack.tryAdd(5))
+ assertTrue(stack.tryAdd(2))
+ assertEquals(2, stack.poll())
+ assertEquals(5, stack.poll())
+ assertEquals(10, stack.poll())
+ }
+}
diff --git a/opendc-simulator/opendc-simulator-flow/src/test/kotlin/org/opendc/simulator/flow2/mux/ForwardingFlowMultiplexerTest.kt b/opendc-simulator/opendc-simulator-flow/src/test/kotlin/org/opendc/simulator/flow2/mux/ForwardingFlowMultiplexerTest.kt
new file mode 100644
index 00000000..a2ed2195
--- /dev/null
+++ b/opendc-simulator/opendc-simulator-flow/src/test/kotlin/org/opendc/simulator/flow2/mux/ForwardingFlowMultiplexerTest.kt
@@ -0,0 +1,66 @@
+/*
+ * Copyright (c) 2022 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.flow2.mux
+
+import org.junit.jupiter.api.Assertions.assertEquals
+import org.junit.jupiter.api.Test
+import org.junit.jupiter.api.assertAll
+import org.opendc.simulator.flow2.FlowEngine
+import org.opendc.simulator.flow2.sink.SimpleFlowSink
+import org.opendc.simulator.flow2.source.TraceFlowSource
+import org.opendc.simulator.kotlin.runSimulation
+
+/**
+ * Test suite for the [ForwardingFlowMultiplexer] class.
+ */
+class ForwardingFlowMultiplexerTest {
+ /**
+ * Test a trace workload.
+ */
+ @Test
+ fun testTrace() = runSimulation {
+ val engine = FlowEngine.create(coroutineContext, clock)
+ val graph = engine.newGraph()
+
+ val switch = ForwardingFlowMultiplexer(graph)
+ val sink = SimpleFlowSink(graph, 3200.0f)
+ graph.connect(switch.newOutput(), sink.input)
+
+ val workload =
+ TraceFlowSource(
+ graph,
+ TraceFlowSource.Trace(
+ longArrayOf(1000, 2000, 3000, 4000),
+ floatArrayOf(28.0f, 3500.0f, 0.0f, 183.0f),
+ 4
+ )
+ )
+ graph.connect(workload.output, switch.newInput())
+
+ advanceUntilIdle()
+
+ assertAll(
+ { assertEquals(4000, clock.millis()) { "Took enough time" } }
+ )
+ }
+}
diff --git a/opendc-simulator/opendc-simulator-flow/src/test/kotlin/org/opendc/simulator/flow/source/FixedFlowSourceTest.kt b/opendc-simulator/opendc-simulator-flow/src/test/kotlin/org/opendc/simulator/flow2/mux/MaxMinFlowMultiplexerTest.kt
index 552579ff..ba339ee3 100644
--- a/opendc-simulator/opendc-simulator-flow/src/test/kotlin/org/opendc/simulator/flow/source/FixedFlowSourceTest.kt
+++ b/opendc-simulator/opendc-simulator-flow/src/test/kotlin/org/opendc/simulator/flow2/mux/MaxMinFlowMultiplexerTest.kt
@@ -1,5 +1,5 @@
/*
- * Copyright (c) 2021 AtLarge Research
+ * Copyright (c) 2022 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,38 +20,35 @@
* SOFTWARE.
*/
-package org.opendc.simulator.flow.source
+package org.opendc.simulator.flow2.mux
import org.junit.jupiter.api.Assertions.assertEquals
import org.junit.jupiter.api.Test
-import org.opendc.simulator.flow.FlowSink
-import org.opendc.simulator.flow.consume
-import org.opendc.simulator.flow.internal.FlowEngineImpl
+import org.opendc.simulator.flow2.FlowEngine
+import org.opendc.simulator.flow2.sink.SimpleFlowSink
+import org.opendc.simulator.flow2.source.SimpleFlowSource
import org.opendc.simulator.kotlin.runSimulation
/**
- * A test suite for the [FixedFlowSource] class.
+ * Test suite for the [MaxMinFlowMultiplexer] class.
*/
-internal class FixedFlowSourceTest {
+class MaxMinFlowMultiplexerTest {
@Test
fun testSmoke() = runSimulation {
- val scheduler = FlowEngineImpl(coroutineContext, clock)
- val provider = FlowSink(scheduler, 1.0)
+ val engine = FlowEngine.create(coroutineContext, clock)
+ val graph = engine.newGraph()
+ val switch = MaxMinFlowMultiplexer(graph)
- val consumer = FixedFlowSource(1.0, 1.0)
+ val sinks = List(2) { SimpleFlowSink(graph, 2000.0f) }
+ for (source in sinks) {
+ graph.connect(switch.newOutput(), source.input)
+ }
- provider.consume(consumer)
- assertEquals(1000, clock.millis())
- }
-
- @Test
- fun testUtilization() = runSimulation {
- val scheduler = FlowEngineImpl(coroutineContext, clock)
- val provider = FlowSink(scheduler, 1.0)
+ val source = SimpleFlowSource(graph, 2000.0f, 1.0f)
+ graph.connect(source.output, switch.newInput())
- val consumer = FixedFlowSource(1.0, 0.5)
+ advanceUntilIdle()
- provider.consume(consumer)
- assertEquals(2000, clock.millis())
+ assertEquals(500, clock.millis())
}
}
diff --git a/opendc-simulator/opendc-simulator-flow/src/test/kotlin/org/opendc/simulator/flow2/sink/FlowSinkTest.kt b/opendc-simulator/opendc-simulator-flow/src/test/kotlin/org/opendc/simulator/flow2/sink/FlowSinkTest.kt
new file mode 100644
index 00000000..a75efba3
--- /dev/null
+++ b/opendc-simulator/opendc-simulator-flow/src/test/kotlin/org/opendc/simulator/flow2/sink/FlowSinkTest.kt
@@ -0,0 +1,124 @@
+/*
+ * Copyright (c) 2022 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.flow2.sink
+
+import kotlinx.coroutines.delay
+import org.junit.jupiter.api.Assertions.assertEquals
+import org.junit.jupiter.api.Test
+import org.opendc.simulator.flow2.FlowEngine
+import org.opendc.simulator.flow2.source.SimpleFlowSource
+import org.opendc.simulator.flow2.source.TraceFlowSource
+import org.opendc.simulator.kotlin.runSimulation
+import java.util.concurrent.ThreadLocalRandom
+
+/**
+ * Test suite for the [SimpleFlowSink] class.
+ */
+class FlowSinkTest {
+ @Test
+ fun testSmoke() = runSimulation {
+ val engine = FlowEngine.create(coroutineContext, clock)
+ val graph = engine.newGraph()
+
+ val sink = SimpleFlowSink(graph, 1.0f)
+ val source = SimpleFlowSource(graph, 2.0f, 1.0f)
+
+ graph.connect(source.output, sink.input)
+ advanceUntilIdle()
+
+ assertEquals(2000, clock.millis())
+ }
+
+ @Test
+ fun testAdjustCapacity() = runSimulation {
+ val engine = FlowEngine.create(coroutineContext, clock)
+ val graph = engine.newGraph()
+
+ val sink = SimpleFlowSink(graph, 1.0f)
+ val source = SimpleFlowSource(graph, 2.0f, 1.0f)
+
+ graph.connect(source.output, sink.input)
+
+ delay(1000)
+ sink.capacity = 0.5f
+
+ advanceUntilIdle()
+
+ assertEquals(3000, clock.millis())
+ }
+
+ @Test
+ fun testUtilization() = runSimulation {
+ val engine = FlowEngine.create(coroutineContext, clock)
+ val graph = engine.newGraph()
+
+ val sink = SimpleFlowSink(graph, 1.0f)
+ val source = SimpleFlowSource(graph, 2.0f, 0.5f)
+
+ graph.connect(source.output, sink.input)
+ advanceUntilIdle()
+
+ assertEquals(4000, clock.millis())
+ }
+
+ @Test
+ fun testFragments() = runSimulation {
+ val engine = FlowEngine.create(coroutineContext, clock)
+ val graph = engine.newGraph()
+
+ val sink = SimpleFlowSink(graph, 1.0f)
+ val trace = TraceFlowSource.Trace(
+ longArrayOf(1000, 2000, 3000, 4000),
+ floatArrayOf(1.0f, 0.5f, 2.0f, 1.0f),
+ 4
+ )
+ val source = TraceFlowSource(
+ graph,
+ trace
+ )
+
+ graph.connect(source.output, sink.input)
+ advanceUntilIdle()
+
+ assertEquals(4000, clock.millis())
+ }
+
+ @Test
+ fun benchmarkSink() {
+ val random = ThreadLocalRandom.current()
+ val traceSize = 10000000
+ val trace = TraceFlowSource.Trace(
+ LongArray(traceSize) { it * 1000L },
+ FloatArray(traceSize) { random.nextDouble(0.0, 4500.0).toFloat() },
+ traceSize
+ )
+
+ return runSimulation {
+ val engine = FlowEngine.create(coroutineContext, clock)
+ val graph = engine.newGraph()
+ val sink = SimpleFlowSink(graph, 4200.0f)
+ val source = TraceFlowSource(graph, trace)
+ graph.connect(source.output, sink.input)
+ }
+ }
+}
diff --git a/opendc-simulator/opendc-simulator-network/src/main/java/org/opendc/simulator/network/SimNetworkLink.java b/opendc-simulator/opendc-simulator-network/src/main/java/org/opendc/simulator/network/SimNetworkLink.java
new file mode 100644
index 00000000..1ea9cb0e
--- /dev/null
+++ b/opendc-simulator/opendc-simulator-network/src/main/java/org/opendc/simulator/network/SimNetworkLink.java
@@ -0,0 +1,77 @@
+/*
+ * Copyright (c) 2022 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 bidirectional communication link between two [SimNetworkPort]s.
+ */
+public final class SimNetworkLink {
+ private final SimNetworkPort left;
+ private final SimNetworkPort right;
+
+ SimNetworkLink(SimNetworkPort left, SimNetworkPort right) {
+ this.left = left;
+ this.right = right;
+ }
+
+ /**
+ * Determine whether the specified <code>port</code> participates in this network link.
+ *
+ * @return <code>true</code> if the port participates in this link, <code>false</code> otherwise.
+ */
+ public boolean contains(SimNetworkPort port) {
+ return port == left || port == right;
+ }
+
+ /**
+ * Obtain the opposite port to which the specified <code>port</code> is connected through this link.
+ */
+ public SimNetworkPort opposite(SimNetworkPort port) {
+ if (port == left) {
+ return right;
+ } else if (port == right) {
+ return left;
+ }
+
+ throw new IllegalArgumentException("Invalid port given");
+ }
+
+ /**
+ * Return the first port of the link.
+ */
+ public SimNetworkPort getLeft() {
+ return left;
+ }
+
+ /**
+ * Return the second port of the link.
+ */
+ public SimNetworkPort getRight() {
+ return right;
+ }
+
+ @Override
+ public String toString() {
+ return "SimNetworkLink[left=" + left + ",right=" + right + "]";
+ }
+}
diff --git a/opendc-simulator/opendc-simulator-network/src/main/java/org/opendc/simulator/network/SimNetworkPort.java b/opendc-simulator/opendc-simulator-network/src/main/java/org/opendc/simulator/network/SimNetworkPort.java
new file mode 100644
index 00000000..b5e09b9b
--- /dev/null
+++ b/opendc-simulator/opendc-simulator-network/src/main/java/org/opendc/simulator/network/SimNetworkPort.java
@@ -0,0 +1,110 @@
+/*
+ * Copyright (c) 2022 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.flow2.Inlet;
+import org.opendc.simulator.flow2.Outlet;
+
+/**
+ * A network port allows network devices to be connected to network through links.
+ */
+public abstract class SimNetworkPort {
+ SimNetworkLink link;
+
+ /**
+ * Determine whether the network port is connected to another port.
+ *
+ * @return <code>true</code> if the network port is connected, <code>false</code> otherwise.
+ */
+ public boolean isConnected() {
+ return link != null;
+ }
+
+ /**
+ * Return network link which connects this port to another port.
+ */
+ public SimNetworkLink getLink() {
+ return link;
+ }
+
+ /**
+ * Connect this port to the specified <code>port</code>.
+ */
+ public void connect(SimNetworkPort port) {
+ if (port == this) {
+ throw new IllegalArgumentException("Circular reference");
+ }
+ if (isConnected()) {
+ throw new IllegalStateException("Port already connected");
+ }
+ if (port.isConnected()) {
+ throw new IllegalStateException("Target port already connected");
+ }
+
+ final SimNetworkLink link = new SimNetworkLink(this, port);
+ this.link = link;
+ port.link = link;
+
+ // Start bidirectional flow channel between the two ports
+ final Outlet outlet = getOutlet();
+ final Inlet inlet = getInlet();
+
+ outlet.getGraph().connect(outlet, port.getInlet());
+ inlet.getGraph().connect(port.getOutlet(), inlet);
+ }
+
+ /**
+ * Disconnect the current network link if it exists.
+ */
+ public void disconnect() {
+ final SimNetworkLink link = this.link;
+ if (link == null) {
+ return;
+ }
+
+ final SimNetworkPort opposite = link.opposite(this);
+ this.link = null;
+ opposite.link = null;
+
+ final Outlet outlet = getOutlet();
+ final Inlet inlet = getInlet();
+
+ outlet.getGraph().disconnect(outlet);
+ inlet.getGraph().disconnect(inlet);
+ }
+
+ /**
+ * Return the {@link Outlet} representing the outgoing traffic of this port.
+ */
+ protected abstract Outlet getOutlet();
+
+ /**
+ * An [Inlet] representing the ingoing traffic of this port.
+ */
+ protected abstract Inlet getInlet();
+
+ @Override
+ public String toString() {
+ return "SimNetworkPort[isConnected=" + isConnected() + "]";
+ }
+}
diff --git a/opendc-simulator/opendc-simulator-network/src/main/java/org/opendc/simulator/network/SimNetworkSink.java b/opendc-simulator/opendc-simulator-network/src/main/java/org/opendc/simulator/network/SimNetworkSink.java
new file mode 100644
index 00000000..f8918328
--- /dev/null
+++ b/opendc-simulator/opendc-simulator-network/src/main/java/org/opendc/simulator/network/SimNetworkSink.java
@@ -0,0 +1,70 @@
+/*
+ * Copyright (c) 2022 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.flow2.FlowGraph;
+import org.opendc.simulator.flow2.Inlet;
+import org.opendc.simulator.flow2.Outlet;
+import org.opendc.simulator.flow2.sink.SimpleFlowSink;
+import org.opendc.simulator.flow2.source.EmptyFlowSource;
+
+/**
+ * A network sink which discards all received traffic and does not generate any traffic itself.
+ */
+public final class SimNetworkSink extends SimNetworkPort {
+ private final EmptyFlowSource source;
+ private final SimpleFlowSink sink;
+
+ /**
+ * Construct a {@link SimNetworkSink} instance.
+ *
+ * @param graph The {@link FlowGraph} to which the sink belongs.
+ * @param capacity The capacity of the sink in terms of processed data.
+ */
+ public SimNetworkSink(FlowGraph graph, float capacity) {
+ this.source = new EmptyFlowSource(graph);
+ this.sink = new SimpleFlowSink(graph, capacity);
+ }
+
+ /**
+ * Return the capacity of the sink.
+ */
+ public float getCapacity() {
+ return sink.getCapacity();
+ }
+
+ @Override
+ protected Outlet getOutlet() {
+ return source.getOutput();
+ }
+
+ @Override
+ protected Inlet getInlet() {
+ return sink.getInput();
+ }
+
+ @Override
+ public String toString() {
+ return "SimNetworkSink[capacity=" + getCapacity() + "]";
+ }
+}
diff --git a/opendc-simulator/opendc-simulator-network/src/main/kotlin/org/opendc/simulator/network/SimNetworkSwitch.kt b/opendc-simulator/opendc-simulator-network/src/main/java/org/opendc/simulator/network/SimNetworkSwitch.java
index 7dc249ab..b05dc53d 100644
--- a/opendc-simulator/opendc-simulator-network/src/main/kotlin/org/opendc/simulator/network/SimNetworkSwitch.kt
+++ b/opendc-simulator/opendc-simulator-network/src/main/java/org/opendc/simulator/network/SimNetworkSwitch.java
@@ -1,5 +1,5 @@
/*
- * Copyright (c) 2021 AtLarge Research
+ * Copyright (c) 2022 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,16 @@
* SOFTWARE.
*/
-package org.opendc.simulator.network
+package org.opendc.simulator.network;
+
+import java.util.List;
/**
* A network device connects devices on a network by switching the traffic over its ports.
*/
public interface SimNetworkSwitch {
/**
- * The ports of the switch.
+ * Return the ports of the switch.
*/
- public val ports: List<SimNetworkPort>
+ List<SimNetworkPort> getPorts();
}
diff --git a/opendc-simulator/opendc-simulator-network/src/main/java/org/opendc/simulator/network/SimNetworkSwitchVirtual.java b/opendc-simulator/opendc-simulator-network/src/main/java/org/opendc/simulator/network/SimNetworkSwitchVirtual.java
new file mode 100644
index 00000000..a94bf799
--- /dev/null
+++ b/opendc-simulator/opendc-simulator-network/src/main/java/org/opendc/simulator/network/SimNetworkSwitchVirtual.java
@@ -0,0 +1,107 @@
+/*
+ * Copyright (c) 2022 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 java.util.ArrayList;
+import java.util.Collections;
+import java.util.List;
+import org.opendc.simulator.flow2.FlowGraph;
+import org.opendc.simulator.flow2.Inlet;
+import org.opendc.simulator.flow2.Outlet;
+import org.opendc.simulator.flow2.mux.FlowMultiplexer;
+import org.opendc.simulator.flow2.mux.MaxMinFlowMultiplexer;
+
+/**
+ * A {@link SimNetworkSwitch} that can support new networking ports on demand.
+ */
+public final class SimNetworkSwitchVirtual implements SimNetworkSwitch {
+ private final List<Port> ports = new ArrayList<>();
+
+ /**
+ * The {@link MaxMinFlowMultiplexer} to actually perform the switching.
+ */
+ private final MaxMinFlowMultiplexer mux;
+
+ /**
+ * Construct a {@link SimNetworkSwitchVirtual} instance.
+ *
+ * @param graph The {@link FlowGraph} to drive the simulation.
+ */
+ public SimNetworkSwitchVirtual(FlowGraph graph) {
+ this.mux = new MaxMinFlowMultiplexer(graph);
+ }
+
+ /**
+ * Open a new port on the switch.
+ */
+ public Port newPort() {
+ final Port port = new Port(mux);
+ ports.add(port);
+ return port;
+ }
+
+ @Override
+ public List<SimNetworkPort> getPorts() {
+ return Collections.unmodifiableList(ports);
+ }
+
+ /**
+ * A port on the network switch.
+ */
+ public class Port extends SimNetworkPort implements AutoCloseable {
+ private final FlowMultiplexer mux;
+ private final Inlet inlet;
+ private final Outlet outlet;
+ private boolean isClosed;
+
+ private Port(FlowMultiplexer mux) {
+ this.mux = mux;
+ this.inlet = mux.newInput();
+ this.outlet = mux.newOutput();
+ }
+
+ @Override
+ protected Outlet getOutlet() {
+ if (isClosed) {
+ throw new IllegalStateException("Port is closed");
+ }
+ return outlet;
+ }
+
+ @Override
+ protected Inlet getInlet() {
+ if (isClosed) {
+ throw new IllegalStateException("Port is closed");
+ }
+ return inlet;
+ }
+
+ @Override
+ public void close() {
+ isClosed = true;
+ mux.releaseInput(inlet);
+ mux.releaseOutput(outlet);
+ ports.remove(this);
+ }
+ }
+}
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
deleted file mode 100644
index 67562640..00000000
--- a/opendc-simulator/opendc-simulator-network/src/main/kotlin/org/opendc/simulator/network/SimNetworkLink.kt
+++ /dev/null
@@ -1,49 +0,0 @@
-/*
- * Copyright (c) 2021 AtLarge Research
- *
- * Permission is hereby granted, free of charge, to any person obtaining a copy
- * of this software and associated documentation files (the "Software"), to deal
- * in the Software without restriction, including without limitation the rights
- * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
- * copies of the Software, and to permit persons to whom the Software is
- * furnished to do so, subject to the following conditions:
- *
- * The above copyright notice and this permission notice shall be included in all
- * copies or substantial portions of the Software.
- *
- * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
- * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
- * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
- * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
- * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
- * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
- * SOFTWARE.
- */
-
-package org.opendc.simulator.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
deleted file mode 100644
index 4b66d5cf..00000000
--- a/opendc-simulator/opendc-simulator-network/src/main/kotlin/org/opendc/simulator/network/SimNetworkPort.kt
+++ /dev/null
@@ -1,91 +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.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/SimNetworkSwitchVirtual.kt b/opendc-simulator/opendc-simulator-network/src/main/kotlin/org/opendc/simulator/network/SimNetworkSwitchVirtual.kt
deleted file mode 100644
index c59c44f1..00000000
--- a/opendc-simulator/opendc-simulator-network/src/main/kotlin/org/opendc/simulator/network/SimNetworkSwitchVirtual.kt
+++ /dev/null
@@ -1,78 +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.network
-
-import org.opendc.simulator.flow.FlowConsumer
-import org.opendc.simulator.flow.FlowEngine
-import org.opendc.simulator.flow.FlowSource
-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/SimNetworkSinkTest.kt b/opendc-simulator/opendc-simulator-network/src/test/kotlin/org/opendc/simulator/network/SimNetworkSinkTest.kt
index 78bd533d..8b4ebb89 100644
--- 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
@@ -24,8 +24,9 @@ package org.opendc.simulator.network
import io.mockk.every
import io.mockk.mockk
-import io.mockk.spyk
import io.mockk.verify
+import kotlinx.coroutines.yield
+import org.junit.jupiter.api.Assertions.assertAll
import org.junit.jupiter.api.Assertions.assertEquals
import org.junit.jupiter.api.Assertions.assertFalse
import org.junit.jupiter.api.Assertions.assertNull
@@ -33,11 +34,7 @@ import org.junit.jupiter.api.Assertions.assertTrue
import org.junit.jupiter.api.Test
import org.junit.jupiter.api.assertDoesNotThrow
import org.junit.jupiter.api.assertThrows
-import org.opendc.simulator.flow.FlowConsumer
-import org.opendc.simulator.flow.FlowEngine
-import org.opendc.simulator.flow.FlowSink
-import org.opendc.simulator.flow.FlowSource
-import org.opendc.simulator.flow.source.FixedFlowSource
+import org.opendc.simulator.flow2.FlowEngine
import org.opendc.simulator.kotlin.runSimulation
/**
@@ -46,18 +43,22 @@ import org.opendc.simulator.kotlin.runSimulation
class SimNetworkSinkTest {
@Test
fun testInitialState() = runSimulation {
- val engine = FlowEngine(coroutineContext, clock)
- val sink = SimNetworkSink(engine, capacity = 100.0)
-
- assertFalse(sink.isConnected)
- assertNull(sink.link)
- assertEquals(100.0, sink.capacity)
+ val engine = FlowEngine.create(coroutineContext, clock)
+ val graph = engine.newGraph()
+ val sink = SimNetworkSink(graph, /*capacity*/ 100.0f)
+
+ assertAll(
+ { assertFalse(sink.isConnected) },
+ { assertNull(sink.link) },
+ { assertEquals(100.0f, sink.capacity) }
+ )
}
@Test
fun testDisconnectIdempotent() = runSimulation {
- val engine = FlowEngine(coroutineContext, clock)
- val sink = SimNetworkSink(engine, capacity = 100.0)
+ val engine = FlowEngine.create(coroutineContext, clock)
+ val graph = engine.newGraph()
+ val sink = SimNetworkSink(graph, /*capacity*/ 100.0f)
assertDoesNotThrow { sink.disconnect() }
assertFalse(sink.isConnected)
@@ -65,8 +66,9 @@ class SimNetworkSinkTest {
@Test
fun testConnectCircular() = runSimulation {
- val engine = FlowEngine(coroutineContext, clock)
- val sink = SimNetworkSink(engine, capacity = 100.0)
+ val engine = FlowEngine.create(coroutineContext, clock)
+ val graph = engine.newGraph()
+ val sink = SimNetworkSink(graph, /*capacity*/ 100.0f)
assertThrows<IllegalArgumentException> {
sink.connect(sink)
@@ -75,8 +77,9 @@ class SimNetworkSinkTest {
@Test
fun testConnectAlreadyConnectedTarget() = runSimulation {
- val engine = FlowEngine(coroutineContext, clock)
- val sink = SimNetworkSink(engine, capacity = 100.0)
+ val engine = FlowEngine.create(coroutineContext, clock)
+ val graph = engine.newGraph()
+ val sink = SimNetworkSink(graph, /*capacity*/ 100.0f)
val source = mockk<SimNetworkPort>(relaxUnitFun = true)
every { source.isConnected } returns true
@@ -87,9 +90,10 @@ class SimNetworkSinkTest {
@Test
fun testConnectAlreadyConnected() = runSimulation {
- val engine = FlowEngine(coroutineContext, clock)
- val sink = SimNetworkSink(engine, capacity = 100.0)
- val source1 = Source(engine)
+ val engine = FlowEngine.create(coroutineContext, clock)
+ val graph = engine.newGraph()
+ val sink = SimNetworkSink(graph, /*capacity*/ 100.0f)
+ val source1 = TestSource(graph)
val source2 = mockk<SimNetworkPort>(relaxUnitFun = true)
@@ -103,41 +107,40 @@ class SimNetworkSinkTest {
@Test
fun testConnect() = runSimulation {
- val engine = FlowEngine(coroutineContext, clock)
- val sink = SimNetworkSink(engine, capacity = 100.0)
- val source = spyk(Source(engine))
- val consumer = source.consumer
+ val engine = FlowEngine.create(coroutineContext, clock)
+ val graph = engine.newGraph()
+ val sink = SimNetworkSink(graph, /*capacity*/ 100.0f)
+ val source = TestSource(graph)
sink.connect(source)
- assertTrue(sink.isConnected)
- assertTrue(source.isConnected)
+ yield()
- verify { source.createConsumer() }
- verify { consumer.onStart(any(), any()) }
+ assertAll(
+ { assertTrue(sink.isConnected) },
+ { assertTrue(source.isConnected) },
+ { assertEquals(100.0f, source.outlet.capacity) }
+ )
+
+ verify { source.logic.onUpdate(any(), any()) }
}
@Test
fun testDisconnect() = runSimulation {
- val engine = FlowEngine(coroutineContext, clock)
- val sink = SimNetworkSink(engine, capacity = 100.0)
- val source = spyk(Source(engine))
- val consumer = source.consumer
+ val engine = FlowEngine.create(coroutineContext, clock)
+ val graph = engine.newGraph()
+ val sink = SimNetworkSink(graph, /*capacity*/ 100.0f)
+ val source = TestSource(graph)
sink.connect(source)
sink.disconnect()
- assertFalse(sink.isConnected)
- assertFalse(source.isConnected)
-
- verify { consumer.onStop(any(), any()) }
- }
-
- private class Source(engine: FlowEngine) : SimNetworkPort() {
- val consumer = spyk(FixedFlowSource(Double.POSITIVE_INFINITY, utilization = 0.8))
-
- public override fun createConsumer(): FlowSource = consumer
+ yield()
- override val provider: FlowConsumer = FlowSink(engine, 0.0)
+ assertAll(
+ { assertFalse(sink.isConnected) },
+ { assertFalse(source.isConnected) },
+ { assertEquals(0.0f, source.outlet.capacity) }
+ )
}
}
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
index ecf80818..1507c4a1 100644
--- 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
@@ -22,16 +22,14 @@
package org.opendc.simulator.network
-import io.mockk.spyk
import io.mockk.verify
+import kotlinx.coroutines.yield
+import org.junit.jupiter.api.Assertions.assertAll
+import org.junit.jupiter.api.Assertions.assertEquals
import org.junit.jupiter.api.Assertions.assertTrue
import org.junit.jupiter.api.Test
import org.junit.jupiter.api.assertThrows
-import org.opendc.simulator.flow.FlowConsumer
-import org.opendc.simulator.flow.FlowEngine
-import org.opendc.simulator.flow.FlowSink
-import org.opendc.simulator.flow.FlowSource
-import org.opendc.simulator.flow.source.FixedFlowSource
+import org.opendc.simulator.flow2.FlowEngine
import org.opendc.simulator.kotlin.runSimulation
/**
@@ -40,27 +38,32 @@ import org.opendc.simulator.kotlin.runSimulation
class SimNetworkSwitchVirtualTest {
@Test
fun testConnect() = runSimulation {
- 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
+ val engine = FlowEngine.create(coroutineContext, clock)
+ val graph = engine.newGraph()
+ val sink = SimNetworkSink(graph, /*capacity*/ 100.0f)
+ val source = TestSource(graph)
+ val switch = SimNetworkSwitchVirtual(graph)
switch.newPort().connect(sink)
switch.newPort().connect(source)
- assertTrue(sink.isConnected)
- assertTrue(source.isConnected)
+ yield()
- verify { source.createConsumer() }
- verify { consumer.onStart(any(), any()) }
+ assertAll(
+ { assertTrue(sink.isConnected) },
+ { assertTrue(source.isConnected) },
+ { assertEquals(100.0f, source.outlet.capacity) }
+ )
+
+ verify { source.logic.onUpdate(any(), any()) }
}
@Test
fun testConnectClosedPort() = runSimulation {
- val engine = FlowEngine(coroutineContext, clock)
- val sink = SimNetworkSink(engine, capacity = 100.0)
- val switch = SimNetworkSwitchVirtual(engine)
+ val engine = FlowEngine.create(coroutineContext, clock)
+ val graph = engine.newGraph()
+ val sink = SimNetworkSink(graph, /*capacity*/ 100.0f)
+ val switch = SimNetworkSwitchVirtual(graph)
val port = switch.newPort()
port.close()
@@ -69,12 +72,4 @@ class SimNetworkSwitchVirtualTest {
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-network/src/main/kotlin/org/opendc/simulator/network/SimNetworkSink.kt b/opendc-simulator/opendc-simulator-network/src/test/kotlin/org/opendc/simulator/network/TestSource.kt
index 684b4a14..f69db7a2 100644
--- a/opendc-simulator/opendc-simulator-network/src/main/kotlin/org/opendc/simulator/network/SimNetworkSink.kt
+++ b/opendc-simulator/opendc-simulator-network/src/test/kotlin/org/opendc/simulator/network/TestSource.kt
@@ -1,5 +1,5 @@
/*
- * Copyright (c) 2021 AtLarge Research
+ * Copyright (c) 2022 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
@@ -22,26 +22,32 @@
package org.opendc.simulator.network
-import org.opendc.simulator.flow.FlowConnection
-import org.opendc.simulator.flow.FlowConsumer
-import org.opendc.simulator.flow.FlowEngine
-import org.opendc.simulator.flow.FlowSink
-import org.opendc.simulator.flow.FlowSource
+import io.mockk.spyk
+import org.opendc.simulator.flow2.FlowGraph
+import org.opendc.simulator.flow2.FlowStage
+import org.opendc.simulator.flow2.FlowStageLogic
+import org.opendc.simulator.flow2.InPort
+import org.opendc.simulator.flow2.Inlet
+import org.opendc.simulator.flow2.OutPort
+import org.opendc.simulator.flow2.Outlet
/**
- * A network sink which discards all received traffic and does not generate any traffic itself.
+ * A [SimNetworkPort] that acts as a test source.
*/
-public class SimNetworkSink(
- engine: FlowEngine,
- public val capacity: Double
-) : SimNetworkPort() {
- override fun createConsumer(): FlowSource = object : FlowSource {
- override fun onPull(conn: FlowConnection, now: Long): Long = Long.MAX_VALUE
-
- override fun toString(): String = "SimNetworkSink.Consumer"
+class TestSource(graph: FlowGraph) : SimNetworkPort(), FlowStageLogic {
+ val logic = spyk(this)
+ private val stage = graph.newStage(logic)
+
+ val outlet: OutPort = stage.getOutlet("out")
+ val inlet: InPort = stage.getInlet("in")
+
+ init {
+ outlet.push(80.0f)
}
- override val provider: FlowConsumer = FlowSink(engine, capacity)
+ override fun onUpdate(ctx: FlowStage, now: Long): Long = Long.MAX_VALUE
+
+ override fun getOutlet(): Outlet = outlet
- override fun toString(): String = "SimNetworkSink[capacity=$capacity]"
+ override fun getInlet(): Inlet = inlet
}
diff --git a/opendc-simulator/opendc-simulator-power/src/main/java/org/opendc/simulator/power/SimPdu.java b/opendc-simulator/opendc-simulator-power/src/main/java/org/opendc/simulator/power/SimPdu.java
new file mode 100644
index 00000000..8790a2d7
--- /dev/null
+++ b/opendc-simulator/opendc-simulator-power/src/main/java/org/opendc/simulator/power/SimPdu.java
@@ -0,0 +1,141 @@
+/*
+ * Copyright (c) 2022 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.jetbrains.annotations.NotNull;
+import org.opendc.simulator.flow2.FlowGraph;
+import org.opendc.simulator.flow2.Inlet;
+import org.opendc.simulator.flow2.Outlet;
+import org.opendc.simulator.flow2.mux.FlowMultiplexer;
+import org.opendc.simulator.flow2.mux.MaxMinFlowMultiplexer;
+import org.opendc.simulator.flow2.util.FlowTransform;
+import org.opendc.simulator.flow2.util.FlowTransformer;
+
+/**
+ * A model of a Power Distribution Unit (PDU).
+ */
+public final class SimPdu extends SimPowerInlet {
+ /**
+ * The {@link FlowMultiplexer} that distributes the electricity over the PDU outlets.
+ */
+ private final MaxMinFlowMultiplexer mux;
+
+ /**
+ * A {@link FlowTransformer} that applies the power loss to the PDU's power inlet.
+ */
+ private final FlowTransformer transformer;
+
+ /**
+ * Construct a {@link SimPdu} instance.
+ *
+ * @param graph The underlying {@link FlowGraph} to which the PDU belongs.
+ * @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 SimPdu(FlowGraph graph, float idlePower, float lossCoefficient) {
+ this.mux = new MaxMinFlowMultiplexer(graph);
+ this.transformer = new FlowTransformer(graph, new FlowTransform() {
+ @Override
+ public float apply(float value) {
+ // See https://download.schneider-electric.com/files?p_Doc_Ref=SPD_NRAN-66CK3D_EN
+ return value * (lossCoefficient * value + 1) + idlePower;
+ }
+
+ @Override
+ public float applyInverse(float value) {
+ float c = lossCoefficient;
+ if (c != 0.f) {
+ return (float) (1 + Math.sqrt(4 * value * c - 4 * idlePower * c + 1)) / (2 * c);
+ } else {
+ return value - idlePower;
+ }
+ }
+ });
+
+ graph.connect(mux.newOutput(), transformer.getInput());
+ }
+
+ /**
+ * Construct a {@link SimPdu} instance without any loss.
+ *
+ * @param graph The underlying {@link FlowGraph} to which the PDU belongs.
+ */
+ public SimPdu(FlowGraph graph) {
+ this(graph, 0.f, 0.f);
+ }
+
+ /**
+ * Create a new PDU outlet.
+ */
+ public PowerOutlet newOutlet() {
+ return new PowerOutlet(mux);
+ }
+
+ @NotNull
+ @Override
+ public Outlet getFlowOutlet() {
+ return transformer.getOutput();
+ }
+
+ @Override
+ public String toString() {
+ return "SimPdu";
+ }
+
+ /**
+ * A PDU outlet.
+ */
+ public static final class PowerOutlet extends SimPowerOutlet implements AutoCloseable {
+ private final FlowMultiplexer mux;
+ private final Inlet inlet;
+ private boolean isClosed;
+
+ private PowerOutlet(FlowMultiplexer mux) {
+ this.mux = mux;
+ this.inlet = mux.newInput();
+ }
+
+ /**
+ * Remove the outlet from the PDU.
+ */
+ @Override
+ public void close() {
+ isClosed = true;
+ mux.releaseInput(inlet);
+ }
+
+ @Override
+ public String toString() {
+ return "SimPdu.Outlet";
+ }
+
+ @NotNull
+ @Override
+ protected Inlet getFlowInlet() {
+ if (isClosed) {
+ throw new IllegalStateException("Outlet is closed");
+ }
+ return inlet;
+ }
+ }
+}
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/java/org/opendc/simulator/power/SimPowerInlet.java
index de587b7f..a6e167c2 100644
--- a/opendc-simulator/opendc-simulator-power/src/main/kotlin/org/opendc/simulator/power/SimPowerInlet.kt
+++ b/opendc-simulator/opendc-simulator-power/src/main/java/org/opendc/simulator/power/SimPowerInlet.java
@@ -1,5 +1,5 @@
/*
- * Copyright (c) 2021 AtLarge Research
+ * Copyright (c) 2022 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,29 +20,34 @@
* SOFTWARE.
*/
-package org.opendc.simulator.power
+package org.opendc.simulator.power;
-import org.opendc.simulator.flow.FlowSource
+import org.opendc.simulator.flow2.Outlet;
/**
* An abstract inlet that consumes electricity from a power outlet.
*/
public abstract class SimPowerInlet {
+ SimPowerOutlet outlet;
+
/**
- * A flag to indicate that the inlet is currently connected to an outlet.
+ * Determine whether the inlet is connected to a {@link SimPowerOutlet}.
+ *
+ * @return <code>true</code> if the inlet is connected to an outlet, <code>false</code> otherwise.
*/
- public val isConnected: Boolean
- get() = _outlet != null
+ public boolean isConnected() {
+ return outlet != null;
+ }
/**
- * The [SimPowerOutlet] to which the inlet is connected.
+ * Return the {@link SimPowerOutlet} to which the inlet is connected.
*/
- public val outlet: SimPowerOutlet?
- get() = _outlet
- internal var _outlet: SimPowerOutlet? = null
+ public SimPowerOutlet getOutlet() {
+ return outlet;
+ }
/**
- * Create a [FlowSource] which represents the consumption of electricity from the power outlet.
+ * Return the flow {@link Outlet} that models the consumption of a power inlet as flow output.
*/
- public abstract fun createSource(): FlowSource
+ protected abstract Outlet getFlowOutlet();
}
diff --git a/opendc-simulator/opendc-simulator-power/src/main/java/org/opendc/simulator/power/SimPowerOutlet.java b/opendc-simulator/opendc-simulator-power/src/main/java/org/opendc/simulator/power/SimPowerOutlet.java
new file mode 100644
index 00000000..e33d35d0
--- /dev/null
+++ b/opendc-simulator/opendc-simulator-power/src/main/java/org/opendc/simulator/power/SimPowerOutlet.java
@@ -0,0 +1,91 @@
+/*
+ * Copyright (c) 2022 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.flow2.Inlet;
+import org.opendc.simulator.flow2.Outlet;
+
+/**
+ * An abstract outlet that provides a source of electricity for datacenter components.
+ */
+public abstract class SimPowerOutlet {
+ private SimPowerInlet inlet;
+
+ /**
+ * Determine whether the outlet is connected to a {@link SimPowerInlet}.
+ *
+ * @return <code>true</code> if the outlet is connected to an inlet, <code>false</code> otherwise.
+ */
+ public boolean isConnected() {
+ return inlet != null;
+ }
+
+ /**
+ * Return the {@link SimPowerInlet} to which the outlet is connected.
+ */
+ public SimPowerInlet getInlet() {
+ return inlet;
+ }
+
+ /**
+ * Connect the specified power [inlet] to this outlet.
+ *
+ * @param inlet The inlet to connect to the outlet.
+ */
+ public void connect(SimPowerInlet inlet) {
+ if (isConnected()) {
+ throw new IllegalStateException("Outlet already connected");
+ }
+ if (inlet.isConnected()) {
+ throw new IllegalStateException("Inlet already connected");
+ }
+
+ this.inlet = inlet;
+ this.inlet.outlet = this;
+
+ final Inlet flowInlet = getFlowInlet();
+ final Outlet flowOutlet = inlet.getFlowOutlet();
+
+ flowInlet.getGraph().connect(flowOutlet, flowInlet);
+ }
+
+ /**
+ * Disconnect the connected power outlet from this inlet
+ */
+ public void disconnect() {
+ SimPowerInlet inlet = this.inlet;
+ if (inlet != null) {
+ this.inlet = null;
+ assert inlet.outlet == this : "Inlet state incorrect";
+ inlet.outlet = null;
+
+ final Inlet flowInlet = getFlowInlet();
+ flowInlet.getGraph().disconnect(flowInlet);
+ }
+ }
+
+ /**
+ * Return the flow {@link Inlet} that models the consumption of a power outlet as flow input.
+ */
+ protected abstract Inlet getFlowInlet();
+}
diff --git a/opendc-simulator/opendc-simulator-power/src/main/java/org/opendc/simulator/power/SimPowerSource.java b/opendc-simulator/opendc-simulator-power/src/main/java/org/opendc/simulator/power/SimPowerSource.java
new file mode 100644
index 00000000..a2d62c48
--- /dev/null
+++ b/opendc-simulator/opendc-simulator-power/src/main/java/org/opendc/simulator/power/SimPowerSource.java
@@ -0,0 +1,71 @@
+/*
+ * Copyright (c) 2022 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.flow2.FlowGraph;
+import org.opendc.simulator.flow2.Inlet;
+import org.opendc.simulator.flow2.sink.SimpleFlowSink;
+
+/**
+ * A {@link SimPowerOutlet} that represents a source of electricity with a maximum capacity.
+ */
+public final class SimPowerSource extends SimPowerOutlet {
+ /**
+ * The resource source that drives this power source.
+ */
+ private final SimpleFlowSink sink;
+
+ /**
+ * Construct a {@link SimPowerSource} instance.
+ *
+ * @param graph The underlying {@link FlowGraph} to which the power source belongs.
+ * @param capacity The maximum amount of power provided by the source.
+ */
+ public SimPowerSource(FlowGraph graph, float capacity) {
+ this.sink = new SimpleFlowSink(graph, capacity);
+ }
+
+ /**
+ * Return the capacity of the power source.
+ */
+ public float getCapacity() {
+ return sink.getCapacity();
+ }
+
+ /**
+ * Return the power draw at this instant.
+ */
+ public float getPowerDraw() {
+ return sink.getRate();
+ }
+
+ @Override
+ protected Inlet getFlowInlet() {
+ return sink.getInput();
+ }
+
+ @Override
+ public String toString() {
+ return "SimPowerSource";
+ }
+}
diff --git a/opendc-simulator/opendc-simulator-power/src/main/java/org/opendc/simulator/power/SimUps.java b/opendc-simulator/opendc-simulator-power/src/main/java/org/opendc/simulator/power/SimUps.java
new file mode 100644
index 00000000..df7508d9
--- /dev/null
+++ b/opendc-simulator/opendc-simulator-power/src/main/java/org/opendc/simulator/power/SimUps.java
@@ -0,0 +1,137 @@
+/*
+ * Copyright (c) 2022 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.jetbrains.annotations.NotNull;
+import org.opendc.simulator.flow2.FlowGraph;
+import org.opendc.simulator.flow2.Inlet;
+import org.opendc.simulator.flow2.Outlet;
+import org.opendc.simulator.flow2.mux.FlowMultiplexer;
+import org.opendc.simulator.flow2.mux.MaxMinFlowMultiplexer;
+import org.opendc.simulator.flow2.util.FlowTransform;
+import org.opendc.simulator.flow2.util.FlowTransformer;
+
+/**
+ * A model of an Uninterruptible Power Supply (UPS).
+ * <p>
+ * This model aggregates multiple power sources into a single source in order to ensure that power is always available.
+ */
+public final class SimUps extends SimPowerOutlet {
+ /**
+ * The {@link FlowMultiplexer} that distributes the electricity over the PDU outlets.
+ */
+ private final MaxMinFlowMultiplexer mux;
+
+ /**
+ * A {@link FlowTransformer} that applies the power loss to the PDU's power inlet.
+ */
+ private final FlowTransformer transformer;
+
+ /**
+ * Construct a {@link SimUps} instance.
+ *
+ * @param graph The underlying {@link FlowGraph} to which the UPS belongs.
+ * @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 SimUps(FlowGraph graph, float idlePower, float lossCoefficient) {
+ this.mux = new MaxMinFlowMultiplexer(graph);
+ this.transformer = new FlowTransformer(graph, new FlowTransform() {
+ @Override
+ public float apply(float value) {
+ // See https://download.schneider-electric.com/files?p_Doc_Ref=SPD_NRAN-66CK3D_EN
+ return value * (lossCoefficient + 1) + idlePower;
+ }
+
+ @Override
+ public float applyInverse(float value) {
+ return (value - idlePower) / (lossCoefficient + 1);
+ }
+ });
+
+ graph.connect(transformer.getOutput(), mux.newInput());
+ }
+
+ /**
+ * Construct a {@link SimUps} instance without any loss.
+ *
+ * @param graph The underlying {@link FlowGraph} to which the UPS belongs.
+ */
+ public SimUps(FlowGraph graph) {
+ this(graph, 0.f, 0.f);
+ }
+
+ /**
+ * Create a new UPS inlet.
+ */
+ public PowerInlet newInlet() {
+ return new PowerInlet(mux);
+ }
+
+ @Override
+ protected Inlet getFlowInlet() {
+ return transformer.getInput();
+ }
+
+ @Override
+ public String toString() {
+ return "SimUps";
+ }
+
+ /**
+ * A UPS inlet.
+ */
+ public static final class PowerInlet extends SimPowerInlet implements AutoCloseable {
+ private final FlowMultiplexer mux;
+ private final Outlet outlet;
+ private boolean isClosed;
+
+ private PowerInlet(FlowMultiplexer mux) {
+ this.mux = mux;
+ this.outlet = mux.newOutput();
+ }
+
+ /**
+ * Remove the inlet from the PDU.
+ */
+ @Override
+ public void close() {
+ isClosed = true;
+ mux.releaseOutput(outlet);
+ }
+
+ @Override
+ public String toString() {
+ return "SimPdu.Inlet";
+ }
+
+ @NotNull
+ @Override
+ protected Outlet getFlowOutlet() {
+ if (isClosed) {
+ throw new IllegalStateException("Inlet is closed");
+ }
+ return outlet;
+ }
+ }
+}
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
deleted file mode 100644
index c4076310..00000000
--- a/opendc-simulator/opendc-simulator-power/src/main/kotlin/org/opendc/simulator/power/SimPdu.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.power
-
-import org.opendc.simulator.flow.FlowConsumer
-import org.opendc.simulator.flow.FlowEngine
-import org.opendc.simulator.flow.FlowMapper
-import org.opendc.simulator.flow.FlowSource
-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/SimPowerOutlet.kt b/opendc-simulator/opendc-simulator-power/src/main/kotlin/org/opendc/simulator/power/SimPowerOutlet.kt
deleted file mode 100644
index 72f52acc..00000000
--- a/opendc-simulator/opendc-simulator-power/src/main/kotlin/org/opendc/simulator/power/SimPowerOutlet.kt
+++ /dev/null
@@ -1,80 +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.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-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
deleted file mode 100644
index 0431d3cf..00000000
--- a/opendc-simulator/opendc-simulator-power/src/main/kotlin/org/opendc/simulator/power/SimUps.kt
+++ /dev/null
@@ -1,101 +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.power
-
-import org.opendc.simulator.flow.FlowEngine
-import org.opendc.simulator.flow.FlowForwarder
-import org.opendc.simulator.flow.FlowMapper
-import org.opendc.simulator.flow.FlowSource
-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
index 29c50d3f..6adb0548 100644
--- 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
@@ -22,14 +22,11 @@
package org.opendc.simulator.power
-import io.mockk.spyk
-import io.mockk.verify
+import kotlinx.coroutines.yield
import org.junit.jupiter.api.Assertions.assertEquals
import org.junit.jupiter.api.Test
import org.junit.jupiter.api.assertThrows
-import org.opendc.simulator.flow.FlowEngine
-import org.opendc.simulator.flow.FlowSource
-import org.opendc.simulator.flow.source.FixedFlowSource
+import org.opendc.simulator.flow2.FlowEngine
import org.opendc.simulator.kotlin.runSimulation
/**
@@ -38,82 +35,93 @@ import org.opendc.simulator.kotlin.runSimulation
internal class SimPduTest {
@Test
fun testZeroOutlets() = runSimulation {
- val engine = FlowEngine(coroutineContext, clock)
- val source = SimPowerSource(engine, capacity = 100.0)
- val pdu = SimPdu(engine)
+ val engine = FlowEngine.create(coroutineContext, clock)
+ val graph = engine.newGraph()
+ val source = SimPowerSource(graph, /*capacity*/ 100.0f)
+ val pdu = SimPdu(graph)
source.connect(pdu)
- assertEquals(0.0, source.powerDraw)
+ yield()
+
+ assertEquals(0.0f, source.powerDraw)
}
@Test
fun testSingleOutlet() = runSimulation {
- val engine = FlowEngine(coroutineContext, clock)
- val source = SimPowerSource(engine, capacity = 100.0)
- val pdu = SimPdu(engine)
+ val engine = FlowEngine.create(coroutineContext, clock)
+ val graph = engine.newGraph()
+ val source = SimPowerSource(graph, /*capacity*/ 100.0f)
+ val pdu = SimPdu(graph)
source.connect(pdu)
- pdu.newOutlet().connect(SimpleInlet())
+ pdu.newOutlet().connect(TestInlet(graph))
+
+ yield()
- assertEquals(50.0, source.powerDraw)
+ assertEquals(100.0f, source.powerDraw)
}
@Test
fun testDoubleOutlet() = runSimulation {
- val engine = FlowEngine(coroutineContext, clock)
- val source = SimPowerSource(engine, capacity = 100.0)
- val pdu = SimPdu(engine)
+ val engine = FlowEngine.create(coroutineContext, clock)
+ val graph = engine.newGraph()
+ val source = SimPowerSource(graph, /*capacity*/ 200.0f)
+ val pdu = SimPdu(graph)
source.connect(pdu)
- pdu.newOutlet().connect(SimpleInlet())
- pdu.newOutlet().connect(SimpleInlet())
+ pdu.newOutlet().connect(TestInlet(graph))
+ pdu.newOutlet().connect(TestInlet(graph))
+
+ yield()
- assertEquals(100.0, source.powerDraw)
+ assertEquals(200.0f, source.powerDraw)
}
@Test
fun testDisconnect() = runSimulation {
- val engine = FlowEngine(coroutineContext, clock)
- val source = SimPowerSource(engine, capacity = 100.0)
- val pdu = SimPdu(engine)
+ val engine = FlowEngine.create(coroutineContext, clock)
+ val graph = engine.newGraph()
+ val source = SimPowerSource(graph, /*capacity*/ 300.0f)
+ val pdu = SimPdu(graph)
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.connect(TestInlet(graph))
outlet.disconnect()
- verify { consumer.onStop(any(), any()) }
+ yield()
+
+ assertEquals(0.0f, source.powerDraw)
}
@Test
fun testLoss() = runSimulation {
- val engine = FlowEngine(coroutineContext, clock)
- val source = SimPowerSource(engine, capacity = 100.0)
+ val engine = FlowEngine.create(coroutineContext, clock)
+ val graph = engine.newGraph()
+ val source = SimPowerSource(graph, /*capacity*/ 500.0f)
// https://download.schneider-electric.com/files?p_Doc_Ref=SPD_NRAN-66CK3D_EN
- val pdu = SimPdu(engine, idlePower = 1.5, lossCoefficient = 0.015)
+ val pdu = SimPdu(graph, /*idlePower*/ 1.5f, /*lossCoefficient*/ 0.015f)
source.connect(pdu)
- pdu.newOutlet().connect(SimpleInlet())
- assertEquals(89.0, source.powerDraw, 0.01)
+ pdu.newOutlet().connect(TestInlet(graph))
+
+ yield()
+
+ assertEquals(251.5f, source.powerDraw, 0.01f)
}
@Test
fun testOutletClose() = runSimulation {
- val engine = FlowEngine(coroutineContext, clock)
- val source = SimPowerSource(engine, capacity = 100.0)
- val pdu = SimPdu(engine)
+ val engine = FlowEngine.create(coroutineContext, clock)
+ val graph = engine.newGraph()
+ val source = SimPowerSource(graph, /*capacity*/ 100.0f)
+ val pdu = SimPdu(graph)
source.connect(pdu)
val outlet = pdu.newOutlet()
outlet.close()
+ yield()
+
assertThrows<IllegalStateException> {
- outlet.connect(SimpleInlet())
+ outlet.connect(TestInlet(graph))
}
}
-
- 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
index 963ba710..03b8182c 100644
--- 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
@@ -24,8 +24,8 @@ package org.opendc.simulator.power
import io.mockk.every
import io.mockk.mockk
-import io.mockk.spyk
-import io.mockk.verify
+import kotlinx.coroutines.yield
+import org.junit.jupiter.api.Assertions.assertAll
import org.junit.jupiter.api.Assertions.assertEquals
import org.junit.jupiter.api.Assertions.assertFalse
import org.junit.jupiter.api.Assertions.assertNull
@@ -33,9 +33,7 @@ import org.junit.jupiter.api.Assertions.assertTrue
import org.junit.jupiter.api.Test
import org.junit.jupiter.api.assertDoesNotThrow
import org.junit.jupiter.api.assertThrows
-import org.opendc.simulator.flow.FlowEngine
-import org.opendc.simulator.flow.FlowSource
-import org.opendc.simulator.flow.source.FixedFlowSource
+import org.opendc.simulator.flow2.FlowEngine
import org.opendc.simulator.kotlin.runSimulation
/**
@@ -44,18 +42,24 @@ import org.opendc.simulator.kotlin.runSimulation
internal class SimPowerSourceTest {
@Test
fun testInitialState() = runSimulation {
- val engine = FlowEngine(coroutineContext, clock)
- val source = SimPowerSource(engine, capacity = 100.0)
+ val engine = FlowEngine.create(coroutineContext, clock)
+ val graph = engine.newGraph()
+ val source = SimPowerSource(graph, /*capacity*/ 100.0f)
- assertFalse(source.isConnected)
- assertNull(source.inlet)
- assertEquals(100.0, source.capacity)
+ yield()
+
+ assertAll(
+ { assertFalse(source.isConnected) },
+ { assertNull(source.inlet) },
+ { assertEquals(100.0f, source.capacity) }
+ )
}
@Test
fun testDisconnectIdempotent() = runSimulation {
- val engine = FlowEngine(coroutineContext, clock)
- val source = SimPowerSource(engine, capacity = 100.0)
+ val engine = FlowEngine.create(coroutineContext, clock)
+ val graph = engine.newGraph()
+ val source = SimPowerSource(graph, /*capacity*/ 100.0f)
assertDoesNotThrow { source.disconnect() }
assertFalse(source.isConnected)
@@ -63,44 +67,51 @@ internal class SimPowerSourceTest {
@Test
fun testConnect() = runSimulation {
- val engine = FlowEngine(coroutineContext, clock)
- val source = SimPowerSource(engine, capacity = 100.0)
- val inlet = SimpleInlet()
+ val engine = FlowEngine.create(coroutineContext, clock)
+ val graph = engine.newGraph()
+ val source = SimPowerSource(graph, /*capacity*/ 100.0f)
+ val inlet = TestInlet(graph)
source.connect(inlet)
- assertTrue(source.isConnected)
- assertEquals(inlet, source.inlet)
- assertTrue(inlet.isConnected)
- assertEquals(source, inlet.outlet)
- assertEquals(100.0, source.powerDraw)
+ yield()
+
+ assertAll(
+ { assertTrue(source.isConnected) },
+ { assertEquals(inlet, source.inlet) },
+ { assertTrue(inlet.isConnected) },
+ { assertEquals(source, inlet.outlet) },
+ { assertEquals(100.0f, source.powerDraw) }
+ )
}
@Test
fun testDisconnect() = runSimulation {
- 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
- }
+ val engine = FlowEngine.create(coroutineContext, clock)
+ val graph = engine.newGraph()
+ val source = SimPowerSource(graph, /*capacity*/ 100.0f)
+ val inlet = TestInlet(graph)
source.connect(inlet)
source.disconnect()
- verify { consumer.onStop(any(), any()) }
+ yield()
+
+ assertEquals(0.0f, inlet.flowOutlet.capacity)
}
@Test
fun testDisconnectAssertion() = runSimulation {
- val engine = FlowEngine(coroutineContext, clock)
- val source = SimPowerSource(engine, capacity = 100.0)
+ val engine = FlowEngine.create(coroutineContext, clock)
+ val graph = engine.newGraph()
+ val source = SimPowerSource(graph, /*capacity*/ 100.0f)
+
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)
+ every { inlet.flowOutlet } returns TestInlet(graph).flowOutlet
source.connect(inlet)
+ inlet.outlet = null
assertThrows<AssertionError> {
source.disconnect()
@@ -109,13 +120,14 @@ internal class SimPowerSourceTest {
@Test
fun testOutletAlreadyConnected() = runSimulation {
- val engine = FlowEngine(coroutineContext, clock)
- val source = SimPowerSource(engine, capacity = 100.0)
- val inlet = SimpleInlet()
+ val engine = FlowEngine.create(coroutineContext, clock)
+ val graph = engine.newGraph()
+ val source = SimPowerSource(graph, /*capacity*/ 100.0f)
+ val inlet = TestInlet(graph)
source.connect(inlet)
assertThrows<IllegalStateException> {
- source.connect(SimpleInlet())
+ source.connect(TestInlet(graph))
}
assertEquals(inlet, source.inlet)
@@ -123,8 +135,9 @@ internal class SimPowerSourceTest {
@Test
fun testInletAlreadyConnected() = runSimulation {
- val engine = FlowEngine(coroutineContext, clock)
- val source = SimPowerSource(engine, capacity = 100.0)
+ val engine = FlowEngine.create(coroutineContext, clock)
+ val graph = engine.newGraph()
+ val source = SimPowerSource(graph, /*capacity*/ 100.0f)
val inlet = mockk<SimPowerInlet>(relaxUnitFun = true)
every { inlet.isConnected } returns true
@@ -132,8 +145,4 @@ internal class SimPowerSourceTest {
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
index 2b2921d7..0dd7bb05 100644
--- 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
@@ -22,14 +22,11 @@
package org.opendc.simulator.power
-import io.mockk.spyk
-import io.mockk.verify
+import kotlinx.coroutines.yield
import org.junit.jupiter.api.Assertions.assertAll
import org.junit.jupiter.api.Assertions.assertEquals
import org.junit.jupiter.api.Test
-import org.opendc.simulator.flow.FlowEngine
-import org.opendc.simulator.flow.FlowSource
-import org.opendc.simulator.flow.source.FixedFlowSource
+import org.opendc.simulator.flow2.FlowEngine
import org.opendc.simulator.kotlin.runSimulation
/**
@@ -38,64 +35,70 @@ import org.opendc.simulator.kotlin.runSimulation
internal class SimUpsTest {
@Test
fun testSingleInlet() = runSimulation {
- val engine = FlowEngine(coroutineContext, clock)
- val source = SimPowerSource(engine, capacity = 100.0)
- val ups = SimUps(engine)
+ val engine = FlowEngine.create(coroutineContext, clock)
+ val graph = engine.newGraph()
+ val source = SimPowerSource(graph, /*capacity*/ 200.0f)
+ val ups = SimUps(graph)
source.connect(ups.newInlet())
- ups.connect(SimpleInlet())
+ ups.connect(TestInlet(graph))
- assertEquals(50.0, source.powerDraw)
+ yield()
+
+ assertEquals(100.0f, source.powerDraw)
}
@Test
fun testDoubleInlet() = runSimulation {
- val engine = FlowEngine(coroutineContext, clock)
- val source1 = SimPowerSource(engine, capacity = 100.0)
- val source2 = SimPowerSource(engine, capacity = 100.0)
- val ups = SimUps(engine)
+ val engine = FlowEngine.create(coroutineContext, clock)
+ val graph = engine.newGraph()
+ val source1 = SimPowerSource(graph, /*capacity*/ 200.0f)
+ val source2 = SimPowerSource(graph, /*capacity*/ 200.0f)
+ val ups = SimUps(graph)
source1.connect(ups.newInlet())
source2.connect(ups.newInlet())
- ups.connect(SimpleInlet())
+ ups.connect(TestInlet(graph))
+
+ yield()
assertAll(
- { assertEquals(50.0, source1.powerDraw) },
- { assertEquals(50.0, source2.powerDraw) }
+ { assertEquals(50.0f, source1.powerDraw) },
+ { assertEquals(50.0f, source2.powerDraw) }
)
}
@Test
fun testLoss() = runSimulation {
- val engine = FlowEngine(coroutineContext, clock)
- val source = SimPowerSource(engine, capacity = 100.0)
+ val engine = FlowEngine.create(coroutineContext, clock)
+ val graph = engine.newGraph()
+ val source = SimPowerSource(graph, /*capacity*/ 500.0f)
// https://download.schneider-electric.com/files?p_Doc_Ref=SPD_NRAN-66CK3D_EN
- val ups = SimUps(engine, idlePower = 4.0, lossCoefficient = 0.05)
+ val ups = SimUps(graph, /*idlePower*/ 4.0f, /*lossCoefficient*/ 0.05f)
source.connect(ups.newInlet())
- ups.connect(SimpleInlet())
+ ups.connect(TestInlet(graph))
+
+ yield()
- assertEquals(56.5, source.powerDraw)
+ assertEquals(109.0f, source.powerDraw, 0.01f)
}
@Test
fun testDisconnect() = runSimulation {
- val engine = FlowEngine(coroutineContext, clock)
- val source1 = SimPowerSource(engine, capacity = 100.0)
- val source2 = SimPowerSource(engine, capacity = 100.0)
- val ups = SimUps(engine)
+ val engine = FlowEngine.create(coroutineContext, clock)
+ val graph = engine.newGraph()
+ val source1 = SimPowerSource(graph, /*capacity*/ 200.0f)
+ val source2 = SimPowerSource(graph, /*capacity*/ 200.0f)
+ val ups = SimUps(graph)
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
- }
+
+ val inlet = TestInlet(graph)
ups.connect(inlet)
ups.disconnect()
- verify { consumer.onStop(any(), any()) }
- }
+ yield()
- class SimpleInlet : SimPowerInlet() {
- override fun createSource(): FlowSource = FixedFlowSource(100.0, utilization = 0.5)
+ assertEquals(0.0f, inlet.flowOutlet.capacity)
}
}
diff --git a/opendc-simulator/opendc-simulator-compute/src/main/kotlin/org/opendc/simulator/compute/kernel/cpufreq/PowerSaveScalingGovernor.kt b/opendc-simulator/opendc-simulator-power/src/test/kotlin/org/opendc/simulator/power/TestInlet.kt
index 32c0703a..7ba12ed9 100644
--- a/opendc-simulator/opendc-simulator-compute/src/main/kotlin/org/opendc/simulator/compute/kernel/cpufreq/PowerSaveScalingGovernor.kt
+++ b/opendc-simulator/opendc-simulator-power/src/test/kotlin/org/opendc/simulator/power/TestInlet.kt
@@ -1,5 +1,5 @@
/*
- * Copyright (c) 2021 AtLarge Research
+ * Copyright (c) 2022 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,17 +20,29 @@
* SOFTWARE.
*/
-package org.opendc.simulator.compute.kernel.cpufreq
+package org.opendc.simulator.power
+
+import io.mockk.spyk
+import org.opendc.simulator.flow2.FlowGraph
+import org.opendc.simulator.flow2.FlowStage
+import org.opendc.simulator.flow2.FlowStageLogic
+import org.opendc.simulator.flow2.Outlet
/**
- * A CPUFreq [ScalingGovernor] that causes the lowest possible frequency to be requested from the resource.
+ * A test inlet.
*/
-public class PowerSaveScalingGovernor : ScalingGovernor {
- override fun createLogic(policy: ScalingPolicy): ScalingGovernor.Logic = object : ScalingGovernor.Logic {
- override fun onStart() {
- policy.target = policy.min
- }
+class TestInlet(graph: FlowGraph) : SimPowerInlet(), FlowStageLogic {
+ val logic = spyk(this)
+ private val stage = graph.newStage(logic)
+ val flowOutlet = stage.getOutlet("out")
+
+ init {
+ flowOutlet.push(100.0f)
}
- override fun toString(): String = "PowerSaveScalingGovernor"
+ override fun onUpdate(ctx: FlowStage, now: Long): Long = Long.MAX_VALUE
+
+ override fun getFlowOutlet(): Outlet {
+ return flowOutlet
+ }
}