summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--buildSrc/build.gradle.kts2
-rw-r--r--buildSrc/src/main/kotlin/benchmark-conventions.gradle.kts38
-rw-r--r--gradle/libs.versions.toml4
-rw-r--r--opendc-compute/opendc-compute-simulator/src/main/kotlin/org/opendc/compute/simulator/SimHost.kt244
-rw-r--r--opendc-compute/opendc-compute-simulator/src/main/kotlin/org/opendc/compute/simulator/internal/Guest.kt150
-rw-r--r--opendc-compute/opendc-compute-simulator/src/main/kotlin/org/opendc/compute/simulator/internal/HostFaultInjectorImpl.kt2
-rw-r--r--opendc-compute/opendc-compute-simulator/src/test/kotlin/org/opendc/compute/simulator/SimHostTest.kt30
-rw-r--r--opendc-compute/opendc-compute-workload/src/main/kotlin/org/opendc/compute/workload/ComputeWorkloadRunner.kt10
-rw-r--r--opendc-experiments/opendc-experiments-capelin/src/main/kotlin/org/opendc/experiments/capelin/Portfolio.kt2
-rw-r--r--opendc-experiments/opendc-experiments-capelin/src/test/kotlin/org/opendc/experiments/capelin/CapelinIntegrationTest.kt25
-rw-r--r--opendc-experiments/opendc-experiments-tf20/src/main/kotlin/org/opendc/experiments/tf20/core/SimTFDevice.kt56
-rw-r--r--opendc-experiments/opendc-experiments-tf20/src/main/kotlin/org/opendc/experiments/tf20/distribute/Strategy.kt2
-rw-r--r--opendc-faas/opendc-faas-simulator/src/main/kotlin/org/opendc/faas/simulator/SimFunctionDeployer.kt4
-rw-r--r--opendc-simulator/opendc-simulator-compute/build.gradle.kts4
-rw-r--r--opendc-simulator/opendc-simulator-compute/src/jmh/kotlin/org/opendc/simulator/compute/SimMachineBenchmarks.kt36
-rw-r--r--opendc-simulator/opendc-simulator-compute/src/main/kotlin/org/opendc/simulator/compute/SimAbstractMachine.kt53
-rw-r--r--opendc-simulator/opendc-simulator-compute/src/main/kotlin/org/opendc/simulator/compute/SimBareMetalMachine.kt47
-rw-r--r--opendc-simulator/opendc-simulator-compute/src/main/kotlin/org/opendc/simulator/compute/SimMachine.kt2
-rw-r--r--opendc-simulator/opendc-simulator-compute/src/main/kotlin/org/opendc/simulator/compute/SimMachineContext.kt6
-rw-r--r--opendc-simulator/opendc-simulator-compute/src/main/kotlin/org/opendc/simulator/compute/SimMemory.kt4
-rw-r--r--opendc-simulator/opendc-simulator-compute/src/main/kotlin/org/opendc/simulator/compute/SimNetworkInterface.kt8
-rw-r--r--opendc-simulator/opendc-simulator-compute/src/main/kotlin/org/opendc/simulator/compute/SimProcessingUnit.kt4
-rw-r--r--opendc-simulator/opendc-simulator-compute/src/main/kotlin/org/opendc/simulator/compute/SimStorageInterface.kt6
-rw-r--r--opendc-simulator/opendc-simulator-compute/src/main/kotlin/org/opendc/simulator/compute/device/SimPsu.kt39
-rw-r--r--opendc-simulator/opendc-simulator-compute/src/main/kotlin/org/opendc/simulator/compute/kernel/SimAbstractHypervisor.kt168
-rw-r--r--opendc-simulator/opendc-simulator-compute/src/main/kotlin/org/opendc/simulator/compute/kernel/SimFairShareHypervisor.kt79
-rw-r--r--opendc-simulator/opendc-simulator-compute/src/main/kotlin/org/opendc/simulator/compute/kernel/SimFairShareHypervisorProvider.kt17
-rw-r--r--opendc-simulator/opendc-simulator-compute/src/main/kotlin/org/opendc/simulator/compute/kernel/SimHypervisor.kt38
-rw-r--r--opendc-simulator/opendc-simulator-compute/src/main/kotlin/org/opendc/simulator/compute/kernel/SimHypervisorCounters.kt (renamed from opendc-simulator/opendc-simulator-resources/src/main/kotlin/org/opendc/simulator/resources/SimResourceDistributor.kt)28
-rw-r--r--opendc-simulator/opendc-simulator-compute/src/main/kotlin/org/opendc/simulator/compute/kernel/SimHypervisorProvider.kt11
-rw-r--r--opendc-simulator/opendc-simulator-compute/src/main/kotlin/org/opendc/simulator/compute/kernel/SimSpaceSharedHypervisor.kt23
-rw-r--r--opendc-simulator/opendc-simulator-compute/src/main/kotlin/org/opendc/simulator/compute/kernel/SimSpaceSharedHypervisorProvider.kt11
-rw-r--r--opendc-simulator/opendc-simulator-compute/src/main/kotlin/org/opendc/simulator/compute/kernel/SimVirtualMachine.kt (renamed from opendc-simulator/opendc-simulator-resources/src/main/kotlin/org/opendc/simulator/resources/SimResourceEvent.kt)24
-rw-r--r--opendc-simulator/opendc-simulator-compute/src/main/kotlin/org/opendc/simulator/compute/kernel/interference/VmInterferenceDomain.kt4
-rw-r--r--opendc-simulator/opendc-simulator-compute/src/main/kotlin/org/opendc/simulator/compute/kernel/interference/VmInterferenceModel.kt2
-rw-r--r--opendc-simulator/opendc-simulator-compute/src/main/kotlin/org/opendc/simulator/compute/power/PStatePowerDriver.kt2
-rw-r--r--opendc-simulator/opendc-simulator-compute/src/main/kotlin/org/opendc/simulator/compute/power/SimplePowerDriver.kt2
-rw-r--r--opendc-simulator/opendc-simulator-compute/src/main/kotlin/org/opendc/simulator/compute/workload/SimFlopsWorkload.kt4
-rw-r--r--opendc-simulator/opendc-simulator-compute/src/main/kotlin/org/opendc/simulator/compute/workload/SimRuntimeWorkload.kt4
-rw-r--r--opendc-simulator/opendc-simulator-compute/src/main/kotlin/org/opendc/simulator/compute/workload/SimTraceWorkload.kt53
-rw-r--r--opendc-simulator/opendc-simulator-compute/src/main/kotlin/org/opendc/simulator/compute/workload/SimWorkloadLifecycle.kt30
-rw-r--r--opendc-simulator/opendc-simulator-compute/src/test/kotlin/org/opendc/simulator/compute/SimMachineTest.kt48
-rw-r--r--opendc-simulator/opendc-simulator-compute/src/test/kotlin/org/opendc/simulator/compute/device/SimPsuTest.kt10
-rw-r--r--opendc-simulator/opendc-simulator-compute/src/test/kotlin/org/opendc/simulator/compute/kernel/SimFairShareHypervisorTest.kt (renamed from opendc-simulator/opendc-simulator-compute/src/test/kotlin/org/opendc/simulator/compute/kernel/SimHypervisorTest.kt)78
-rw-r--r--opendc-simulator/opendc-simulator-compute/src/test/kotlin/org/opendc/simulator/compute/kernel/SimSpaceSharedHypervisorTest.kt44
-rw-r--r--opendc-simulator/opendc-simulator-compute/src/test/kotlin/org/opendc/simulator/compute/power/PStatePowerDriverTest.kt10
-rw-r--r--opendc-simulator/opendc-simulator-compute/src/test/kotlin/org/opendc/simulator/compute/workload/SimTraceWorkloadTest.kt10
-rw-r--r--opendc-simulator/opendc-simulator-flow/build.gradle.kts (renamed from opendc-simulator/opendc-simulator-resources/build.gradle.kts)6
-rw-r--r--opendc-simulator/opendc-simulator-flow/src/jmh/kotlin/org/opendc/simulator/flow/FlowBenchmarks.kt140
-rw-r--r--opendc-simulator/opendc-simulator-flow/src/main/kotlin/org/opendc/simulator/flow/AbstractFlowConsumer.kt147
-rw-r--r--opendc-simulator/opendc-simulator-flow/src/main/kotlin/org/opendc/simulator/flow/FlowConnection.kt (renamed from opendc-simulator/opendc-simulator-resources/src/main/kotlin/org/opendc/simulator/resources/SimResourceContext.kt)40
-rw-r--r--opendc-simulator/opendc-simulator-flow/src/main/kotlin/org/opendc/simulator/flow/FlowConsumer.kt131
-rw-r--r--opendc-simulator/opendc-simulator-flow/src/main/kotlin/org/opendc/simulator/flow/FlowConsumerContext.kt (renamed from opendc-simulator/opendc-simulator-resources/src/main/kotlin/org/opendc/simulator/resources/SimResourceState.kt)25
-rw-r--r--opendc-simulator/opendc-simulator-flow/src/main/kotlin/org/opendc/simulator/flow/FlowConsumerLogic.kt60
-rw-r--r--opendc-simulator/opendc-simulator-flow/src/main/kotlin/org/opendc/simulator/flow/FlowConvergenceListener.kt (renamed from opendc-simulator/opendc-simulator-resources/src/main/kotlin/org/opendc/simulator/resources/SimResourceAggregator.kt)18
-rw-r--r--opendc-simulator/opendc-simulator-flow/src/main/kotlin/org/opendc/simulator/flow/FlowCounters.kt (renamed from opendc-simulator/opendc-simulator-resources/src/main/kotlin/org/opendc/simulator/resources/SimResourceCounters.kt)21
-rw-r--r--opendc-simulator/opendc-simulator-flow/src/main/kotlin/org/opendc/simulator/flow/FlowEngine.kt (renamed from opendc-simulator/opendc-simulator-resources/src/main/kotlin/org/opendc/simulator/resources/SimResourceInterpreter.kt)30
-rw-r--r--opendc-simulator/opendc-simulator-flow/src/main/kotlin/org/opendc/simulator/flow/FlowForwarder.kt252
-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.kt70
-rw-r--r--opendc-simulator/opendc-simulator-flow/src/main/kotlin/org/opendc/simulator/flow/FlowSource.kt70
-rw-r--r--opendc-simulator/opendc-simulator-flow/src/main/kotlin/org/opendc/simulator/flow/interference/InterferenceDomain.kt (renamed from opendc-simulator/opendc-simulator-resources/src/main/kotlin/org/opendc/simulator/resources/interference/InterferenceDomain.kt)8
-rw-r--r--opendc-simulator/opendc-simulator-flow/src/main/kotlin/org/opendc/simulator/flow/interference/InterferenceKey.kt (renamed from opendc-simulator/opendc-simulator-resources/src/main/kotlin/org/opendc/simulator/resources/interference/InterferenceKey.kt)2
-rw-r--r--opendc-simulator/opendc-simulator-flow/src/main/kotlin/org/opendc/simulator/flow/internal/Flags.kt43
-rw-r--r--opendc-simulator/opendc-simulator-flow/src/main/kotlin/org/opendc/simulator/flow/internal/FlowConsumerContextImpl.kt419
-rw-r--r--opendc-simulator/opendc-simulator-flow/src/main/kotlin/org/opendc/simulator/flow/internal/FlowCountersImpl.kt (renamed from opendc-simulator/opendc-simulator-resources/src/main/kotlin/org/opendc/simulator/resources/impl/SimResourceCountersImpl.kt)18
-rw-r--r--opendc-simulator/opendc-simulator-flow/src/main/kotlin/org/opendc/simulator/flow/internal/FlowEngineImpl.kt247
-rw-r--r--opendc-simulator/opendc-simulator-flow/src/main/kotlin/org/opendc/simulator/flow/mux/FlowMultiplexer.kt100
-rw-r--r--opendc-simulator/opendc-simulator-flow/src/main/kotlin/org/opendc/simulator/flow/mux/ForwardingFlowMultiplexer.kt154
-rw-r--r--opendc-simulator/opendc-simulator-flow/src/main/kotlin/org/opendc/simulator/flow/mux/MaxMinFlowMultiplexer.kt490
-rw-r--r--opendc-simulator/opendc-simulator-flow/src/main/kotlin/org/opendc/simulator/flow/source/FixedFlowSource.kt (renamed from opendc-simulator/opendc-simulator-resources/src/main/kotlin/org/opendc/simulator/resources/consumer/SimWorkConsumer.kt)45
-rw-r--r--opendc-simulator/opendc-simulator-flow/src/main/kotlin/org/opendc/simulator/flow/source/FlowSourceBarrier.kt (renamed from opendc-simulator/opendc-simulator-resources/src/main/kotlin/org/opendc/simulator/resources/consumer/SimConsumerBarrier.kt)8
-rw-r--r--opendc-simulator/opendc-simulator-flow/src/main/kotlin/org/opendc/simulator/flow/source/FlowSourceRateAdapter.kt77
-rw-r--r--opendc-simulator/opendc-simulator-flow/src/main/kotlin/org/opendc/simulator/flow/source/TraceFlowSource.kt67
-rw-r--r--opendc-simulator/opendc-simulator-flow/src/test/kotlin/org/opendc/simulator/flow/FlowConsumerContextTest.kt104
-rw-r--r--opendc-simulator/opendc-simulator-flow/src/test/kotlin/org/opendc/simulator/flow/FlowForwarderTest.kt321
-rw-r--r--opendc-simulator/opendc-simulator-flow/src/test/kotlin/org/opendc/simulator/flow/FlowSinkTest.kt241
-rw-r--r--opendc-simulator/opendc-simulator-flow/src/test/kotlin/org/opendc/simulator/flow/mux/ForwardingFlowMultiplexerTest.kt154
-rw-r--r--opendc-simulator/opendc-simulator-flow/src/test/kotlin/org/opendc/simulator/flow/mux/MaxMinFlowMultiplexerTest.kt (renamed from opendc-simulator/opendc-simulator-resources/src/test/kotlin/org/opendc/simulator/resources/SimResourceSwitchMaxMinTest.kt)91
-rw-r--r--opendc-simulator/opendc-simulator-flow/src/test/kotlin/org/opendc/simulator/flow/source/FixedFlowSourceTest.kt (renamed from opendc-simulator/opendc-simulator-resources/src/test/kotlin/org/opendc/simulator/resources/SimWorkConsumerTest.kt)25
-rw-r--r--opendc-simulator/opendc-simulator-network/build.gradle.kts4
-rw-r--r--opendc-simulator/opendc-simulator-network/src/main/kotlin/org/opendc/simulator/network/SimNetworkPort.kt12
-rw-r--r--opendc-simulator/opendc-simulator-network/src/main/kotlin/org/opendc/simulator/network/SimNetworkSink.kt10
-rw-r--r--opendc-simulator/opendc-simulator-network/src/main/kotlin/org/opendc/simulator/network/SimNetworkSwitchVirtual.kt23
-rw-r--r--opendc-simulator/opendc-simulator-network/src/test/kotlin/org/opendc/simulator/network/SimNetworkSinkTest.kt50
-rw-r--r--opendc-simulator/opendc-simulator-network/src/test/kotlin/org/opendc/simulator/network/SimNetworkSwitchVirtualTest.kt28
-rw-r--r--opendc-simulator/opendc-simulator-power/build.gradle.kts4
-rw-r--r--opendc-simulator/opendc-simulator-power/src/main/kotlin/org/opendc/simulator/power/SimPdu.kt50
-rw-r--r--opendc-simulator/opendc-simulator-power/src/main/kotlin/org/opendc/simulator/power/SimPowerInlet.kt6
-rw-r--r--opendc-simulator/opendc-simulator-power/src/main/kotlin/org/opendc/simulator/power/SimPowerSource.kt14
-rw-r--r--opendc-simulator/opendc-simulator-power/src/main/kotlin/org/opendc/simulator/power/SimUps.kt51
-rw-r--r--opendc-simulator/opendc-simulator-power/src/test/kotlin/org/opendc/simulator/power/SimPduTest.kt51
-rw-r--r--opendc-simulator/opendc-simulator-power/src/test/kotlin/org/opendc/simulator/power/SimPowerSourceTest.kt45
-rw-r--r--opendc-simulator/opendc-simulator-power/src/test/kotlin/org/opendc/simulator/power/SimUpsTest.kt43
-rw-r--r--opendc-simulator/opendc-simulator-resources/src/jmh/kotlin/org/opendc/simulator/resources/SimResourceBenchmarks.kt144
-rw-r--r--opendc-simulator/opendc-simulator-resources/src/main/kotlin/org/opendc/simulator/resources/SimAbstractResourceAggregator.kt211
-rw-r--r--opendc-simulator/opendc-simulator-resources/src/main/kotlin/org/opendc/simulator/resources/SimAbstractResourceProvider.kt128
-rw-r--r--opendc-simulator/opendc-simulator-resources/src/main/kotlin/org/opendc/simulator/resources/SimResourceAggregatorMaxMin.kt74
-rw-r--r--opendc-simulator/opendc-simulator-resources/src/main/kotlin/org/opendc/simulator/resources/SimResourceCloseableProvider.kt37
-rw-r--r--opendc-simulator/opendc-simulator-resources/src/main/kotlin/org/opendc/simulator/resources/SimResourceCommand.kt52
-rw-r--r--opendc-simulator/opendc-simulator-resources/src/main/kotlin/org/opendc/simulator/resources/SimResourceConsumer.kt56
-rw-r--r--opendc-simulator/opendc-simulator-resources/src/main/kotlin/org/opendc/simulator/resources/SimResourceControllableContext.kt64
-rw-r--r--opendc-simulator/opendc-simulator-resources/src/main/kotlin/org/opendc/simulator/resources/SimResourceDistributorMaxMin.kt373
-rw-r--r--opendc-simulator/opendc-simulator-resources/src/main/kotlin/org/opendc/simulator/resources/SimResourceFlow.kt29
-rw-r--r--opendc-simulator/opendc-simulator-resources/src/main/kotlin/org/opendc/simulator/resources/SimResourceProvider.kt106
-rw-r--r--opendc-simulator/opendc-simulator-resources/src/main/kotlin/org/opendc/simulator/resources/SimResourceProviderLogic.kt79
-rw-r--r--opendc-simulator/opendc-simulator-resources/src/main/kotlin/org/opendc/simulator/resources/SimResourceSource.kt72
-rw-r--r--opendc-simulator/opendc-simulator-resources/src/main/kotlin/org/opendc/simulator/resources/SimResourceSwitch.kt57
-rw-r--r--opendc-simulator/opendc-simulator-resources/src/main/kotlin/org/opendc/simulator/resources/SimResourceSwitchExclusive.kt115
-rw-r--r--opendc-simulator/opendc-simulator-resources/src/main/kotlin/org/opendc/simulator/resources/SimResourceSwitchMaxMin.kt102
-rw-r--r--opendc-simulator/opendc-simulator-resources/src/main/kotlin/org/opendc/simulator/resources/SimResourceSystem.kt43
-rw-r--r--opendc-simulator/opendc-simulator-resources/src/main/kotlin/org/opendc/simulator/resources/SimResourceTransformer.kt206
-rw-r--r--opendc-simulator/opendc-simulator-resources/src/main/kotlin/org/opendc/simulator/resources/consumer/SimSpeedConsumerAdapter.kt83
-rw-r--r--opendc-simulator/opendc-simulator-resources/src/main/kotlin/org/opendc/simulator/resources/consumer/SimTraceConsumer.kt73
-rw-r--r--opendc-simulator/opendc-simulator-resources/src/main/kotlin/org/opendc/simulator/resources/impl/SimResourceContextImpl.kt393
-rw-r--r--opendc-simulator/opendc-simulator-resources/src/main/kotlin/org/opendc/simulator/resources/impl/SimResourceInterpreterImpl.kt351
-rw-r--r--opendc-simulator/opendc-simulator-resources/src/test/kotlin/org/opendc/simulator/resources/SimResourceAggregatorMaxMinTest.kt200
-rw-r--r--opendc-simulator/opendc-simulator-resources/src/test/kotlin/org/opendc/simulator/resources/SimResourceCommandTest.kt74
-rw-r--r--opendc-simulator/opendc-simulator-resources/src/test/kotlin/org/opendc/simulator/resources/SimResourceContextTest.kt160
-rw-r--r--opendc-simulator/opendc-simulator-resources/src/test/kotlin/org/opendc/simulator/resources/SimResourceSourceTest.kt273
-rw-r--r--opendc-simulator/opendc-simulator-resources/src/test/kotlin/org/opendc/simulator/resources/SimResourceSwitchExclusiveTest.kt176
-rw-r--r--opendc-simulator/opendc-simulator-resources/src/test/kotlin/org/opendc/simulator/resources/SimResourceTransformerTest.kt222
-rw-r--r--opendc-web/opendc-web-runner/src/main/kotlin/org/opendc/web/runner/Main.kt6
-rw-r--r--opendc-workflow/opendc-workflow-service/src/test/kotlin/org/opendc/workflow/service/WorkflowServiceTest.kt6
-rw-r--r--settings.gradle.kts2
125 files changed, 4466 insertions, 5069 deletions
diff --git a/buildSrc/build.gradle.kts b/buildSrc/build.gradle.kts
index a5e99bfa..4eb8ac31 100644
--- a/buildSrc/build.gradle.kts
+++ b/buildSrc/build.gradle.kts
@@ -34,7 +34,7 @@ dependencies {
implementation(kotlin("gradle-plugin", version = "1.5.30"))
implementation("org.jlleitschuh.gradle:ktlint-gradle:10.1.0")
implementation("org.jetbrains.kotlin:kotlin-allopen:1.5.30")
- implementation("org.jetbrains.kotlinx:kotlinx-benchmark-plugin:0.3.1")
+ implementation("me.champeau.jmh:jmh-gradle-plugin:0.6.6")
implementation("org.jetbrains.dokka:dokka-gradle-plugin:1.5.0")
implementation("gradle.plugin.com.github.jengelman.gradle.plugins:shadow:7.0.0")
}
diff --git a/buildSrc/src/main/kotlin/benchmark-conventions.gradle.kts b/buildSrc/src/main/kotlin/benchmark-conventions.gradle.kts
index 590f51cf..65608e8f 100644
--- a/buildSrc/src/main/kotlin/benchmark-conventions.gradle.kts
+++ b/buildSrc/src/main/kotlin/benchmark-conventions.gradle.kts
@@ -20,49 +20,21 @@
* SOFTWARE.
*/
-import kotlinx.benchmark.gradle.*
import org.jetbrains.kotlin.allopen.gradle.*
plugins {
- id("org.jetbrains.kotlinx.benchmark")
`java-library`
kotlin("plugin.allopen")
-}
-
-sourceSets {
- register("jmh") {
- compileClasspath += sourceSets["main"].output
- runtimeClasspath += sourceSets["main"].output
- }
-}
-
-configurations {
- named("jmhImplementation") {
- extendsFrom(configurations["implementation"])
- }
+ id("me.champeau.jmh")
}
configure<AllOpenExtension> {
annotation("org.openjdk.jmh.annotations.State")
}
-benchmark {
- targets {
- register("jmh") {
- this as JvmBenchmarkTarget
- jmhVersion = "1.33"
- }
- }
-}
-
-dependencies {
- val libs = Libs(project)
- implementation(libs["kotlinx.benchmark.runtime.jvm"])
-}
+jmh {
+ jmhVersion.set("1.33")
-// Workaround for https://github.com/Kotlin/kotlinx-benchmark/issues/39
-afterEvaluate {
- tasks.named<org.gradle.jvm.tasks.Jar>("jmhBenchmarkJar") {
- duplicatesStrategy = DuplicatesStrategy.EXCLUDE
- }
+ profilers.add("stack")
+ profilers.add("gc")
}
diff --git a/gradle/libs.versions.toml b/gradle/libs.versions.toml
index 82da905c..03402b9a 100644
--- a/gradle/libs.versions.toml
+++ b/gradle/libs.versions.toml
@@ -8,7 +8,6 @@ jackson = "2.12.5"
junit-jupiter = "5.7.2"
junit-platform = "1.7.2"
kotlin-logging = "2.0.11"
-kotlinx-benchmark = "0.3.1"
kotlinx-coroutines = "1.5.1"
ktor = "1.6.3"
log4j = "2.14.1"
@@ -65,9 +64,6 @@ ktor-client-auth = { module = "io.ktor:ktor-client-auth", version.ref = "ktor" }
ktor-client-jackson = { module = "io.ktor:ktor-client-jackson", version.ref = "ktor" }
ktor-client-mock = { module = "io.ktor:ktor-client-mock", version.ref = "ktor" }
-# Benchmark
-kotlinx-benchmark-runtime-jvm = { module = "org.jetbrains.kotlinx:kotlinx-benchmark-runtime-jvm", version.ref = "kotlinx-benchmark" }
-
# Other
classgraph = { module = "io.github.classgraph:classgraph", version.ref = "classgraph" }
hadoop-common = { module = "org.apache.hadoop:hadoop-common", version.ref = "hadoop" }
diff --git a/opendc-compute/opendc-compute-simulator/src/main/kotlin/org/opendc/compute/simulator/SimHost.kt b/opendc-compute/opendc-compute-simulator/src/main/kotlin/org/opendc/compute/simulator/SimHost.kt
index fdb3f1dc..b9d02185 100644
--- a/opendc-compute/opendc-compute-simulator/src/main/kotlin/org/opendc/compute/simulator/SimHost.kt
+++ b/opendc-compute/opendc-compute-simulator/src/main/kotlin/org/opendc/compute/simulator/SimHost.kt
@@ -47,11 +47,9 @@ import org.opendc.simulator.compute.model.MemoryUnit
import org.opendc.simulator.compute.power.ConstantPowerModel
import org.opendc.simulator.compute.power.PowerDriver
import org.opendc.simulator.compute.power.SimplePowerDriver
-import org.opendc.simulator.resources.SimResourceDistributorMaxMin
-import org.opendc.simulator.resources.SimResourceInterpreter
+import org.opendc.simulator.flow.FlowEngine
import java.util.*
import kotlin.coroutines.CoroutineContext
-import kotlin.math.roundToLong
/**
* A [Host] that is simulates virtual machines on a physical machine using [SimHypervisor].
@@ -62,9 +60,9 @@ public class SimHost(
model: MachineModel,
override val meta: Map<String, Any>,
context: CoroutineContext,
- interpreter: SimResourceInterpreter,
+ engine: FlowEngine,
meterProvider: MeterProvider,
- hypervisor: SimHypervisorProvider,
+ hypervisorProvider: SimHypervisorProvider,
scalingGovernor: ScalingGovernor = PerformanceScalingGovernor(),
powerDriver: PowerDriver = SimplePowerDriver(ConstantPowerModel(0.0)),
private val mapper: SimWorkloadMapper = SimMetaWorkloadMapper(),
@@ -79,7 +77,7 @@ public class SimHost(
/**
* The clock instance used by the host.
*/
- private val clock = interpreter.clock
+ private val clock = engine.clock
/**
* The logger instance of this server.
@@ -99,39 +97,19 @@ public class SimHost(
/**
* The machine to run on.
*/
- public val machine: SimBareMetalMachine = SimBareMetalMachine(interpreter, model.optimize(), powerDriver)
+ public val machine: SimBareMetalMachine = SimBareMetalMachine(engine, model.optimize(), powerDriver)
/**
* The hypervisor to run multiple workloads.
*/
- private val hypervisor: SimHypervisor = hypervisor.create(
- interpreter,
- scalingGovernor = scalingGovernor,
- interferenceDomain = interferenceDomain,
- listener = object : SimHypervisor.Listener {
- override fun onSliceFinish(
- hypervisor: SimHypervisor,
- totalWork: Double,
- grantedWork: Double,
- overcommittedWork: Double,
- interferedWork: Double,
- cpuUsage: Double,
- cpuDemand: Double
- ) {
- _cpuDemand = cpuDemand
- _cpuUsage = cpuUsage
-
- collectTime()
- }
- }
- )
- private var _cpuUsage = 0.0
- private var _cpuDemand = 0.0
+ private val hypervisor: SimHypervisor = hypervisorProvider
+ .create(engine, scalingGovernor = scalingGovernor, interferenceDomain = interferenceDomain)
/**
* The virtual machines running on the hypervisor.
*/
private val guests = HashMap<Server, Guest>()
+ private val _guests = mutableListOf<Guest>()
override val state: HostState
get() = _state
@@ -158,22 +136,13 @@ public class SimHost(
}
}
+ /**
+ * The [Job] that represents the machine running the hypervisor.
+ */
+ private var _job: Job? = null
+
init {
- // Launch hypervisor onto machine
- scope.launch {
- try {
- _bootTime = clock.millis()
- _state = HostState.UP
- machine.run(this@SimHost.hypervisor, emptyMap())
- } catch (_: CancellationException) {
- // Ignored
- } catch (cause: Throwable) {
- logger.error(cause) { "Host failed" }
- throw cause
- } finally {
- _state = HostState.DOWN
- }
- }
+ launch()
meter.upDownCounterBuilder("system.guests")
.setDescription("Number of guests on this host")
@@ -185,15 +154,15 @@ public class SimHost(
meter.gaugeBuilder("system.cpu.demand")
.setDescription("Amount of CPU resources the guests would use if there were no CPU contention or CPU limits")
.setUnit("MHz")
- .buildWithCallback { result -> result.observe(_cpuDemand) }
+ .buildWithCallback { result -> result.observe(hypervisor.cpuDemand) }
meter.gaugeBuilder("system.cpu.usage")
.setDescription("Amount of CPU resources used by the host")
.setUnit("MHz")
- .buildWithCallback { result -> result.observe(_cpuUsage) }
+ .buildWithCallback { result -> result.observe(hypervisor.cpuUsage) }
meter.gaugeBuilder("system.cpu.utilization")
.setDescription("Utilization of the CPU resources of the host")
.setUnit("%")
- .buildWithCallback { result -> result.observe(_cpuUsage / _cpuLimit) }
+ .buildWithCallback { result -> result.observe(hypervisor.cpuUsage / _cpuLimit) }
meter.counterBuilder("system.cpu.time")
.setDescription("Amount of CPU time spent by the host")
.setUnit("s")
@@ -201,16 +170,16 @@ public class SimHost(
meter.gaugeBuilder("system.power.usage")
.setDescription("Power usage of the host ")
.setUnit("W")
- .buildWithCallback { result -> result.observe(machine.powerDraw) }
+ .buildWithCallback { result -> result.observe(machine.powerUsage) }
meter.counterBuilder("system.power.total")
.setDescription("Amount of energy used by the CPU")
.setUnit("J")
.ofDoubles()
- .buildWithCallback(::collectPowerTotal)
+ .buildWithCallback { result -> result.observe(machine.energyUsage) }
meter.counterBuilder("system.time")
.setDescription("The uptime of the host")
.setUnit("s")
- .buildWithCallback(::collectTime)
+ .buildWithCallback(::collectUptime)
meter.gaugeBuilder("system.time.boot")
.setDescription("The boot time of the host")
.setUnit("1")
@@ -231,7 +200,7 @@ public class SimHost(
require(canFit(key)) { "Server does not fit" }
val machine = hypervisor.createMachine(key.flavor.toMachineModel(), key.name)
- Guest(
+ val newGuest = Guest(
scope.coroutineContext,
clock,
this,
@@ -240,6 +209,9 @@ public class SimHost(
server,
machine
)
+
+ _guests.add(newGuest)
+ newGuest
}
if (start) {
@@ -263,7 +235,7 @@ public class SimHost(
override suspend fun delete(server: Server) {
val guest = guests[server] ?: return
- guest.terminate()
+ guest.delete()
}
override fun addListener(listener: HostListener) {
@@ -285,30 +257,61 @@ public class SimHost(
public suspend fun fail() {
reset()
- for (guest in guests.values) {
+ for (guest in _guests) {
guest.fail()
}
}
public suspend fun recover() {
- collectTime()
- _state = HostState.UP
- _bootTime = clock.millis()
+ updateUptime()
+
+ launch()
+
+ // Wait for the hypervisor to launch before recovering the guests
+ yield()
- for (guest in guests.values) {
+ for (guest in _guests) {
guest.recover()
}
}
/**
+ * Launch the hypervisor.
+ */
+ private fun launch() {
+ check(_job == null) { "Concurrent hypervisor running" }
+
+ // Launch hypervisor onto machine
+ _job = scope.launch {
+ try {
+ _bootTime = clock.millis()
+ _state = HostState.UP
+ machine.run(hypervisor, emptyMap())
+ } catch (_: CancellationException) {
+ // Ignored
+ } catch (cause: Throwable) {
+ logger.error(cause) { "Host failed" }
+ throw cause
+ } finally {
+ _state = HostState.DOWN
+ }
+ }
+ }
+
+ /**
* Reset the machine.
*/
private fun reset() {
- collectTime()
+ updateUptime()
+
+ // Stop the hypervisor
+ val job = _job
+ if (job != null) {
+ job.cancel()
+ _job = null
+ }
_state = HostState.DOWN
- _cpuUsage = 0.0
- _cpuDemand = 0.0
}
/**
@@ -358,11 +361,17 @@ public class SimHost(
var error = 0L
var invalid = 0L
- for ((_, guest) in guests) {
+ val guests = _guests.listIterator()
+ for (guest in guests) {
when (guest.state) {
ServerState.TERMINATED -> terminated++
ServerState.RUNNING -> running++
ServerState.ERROR -> error++
+ ServerState.DELETED -> {
+ // Remove guests that have been deleted
+ this.guests.remove(guest.server)
+ guests.remove()
+ }
else -> invalid++
}
}
@@ -381,24 +390,9 @@ public class SimHost(
private fun collectCpuLimit(result: ObservableDoubleMeasurement) {
result.observe(_cpuLimit)
- for (guest in guests.values) {
- guest.collectCpuLimit(result)
- }
- }
-
- private var _lastCpuTimeCallback = clock.millis()
-
- /**
- * Helper function to track the CPU time of a machine.
- */
- private fun collectCpuTime(result: ObservableLongMeasurement) {
- val now = clock.millis()
- val duration = now - _lastCpuTimeCallback
-
- try {
- collectCpuTime(duration, result)
- } finally {
- _lastCpuTimeCallback = now
+ val guests = _guests
+ for (i in guests.indices) {
+ guests[i].collectCpuLimit(result)
}
}
@@ -406,50 +400,22 @@ public class SimHost(
private val _stealState = Attributes.of(STATE_KEY, "steal")
private val _lostState = Attributes.of(STATE_KEY, "lost")
private val _idleState = Attributes.of(STATE_KEY, "idle")
- private var _totalTime = 0.0
/**
* Helper function to track the CPU time of a machine.
*/
- private fun collectCpuTime(duration: Long, result: ObservableLongMeasurement) {
- val coreCount = this.model.cpuCount
- val d = coreCount / _cpuLimit
-
+ private fun collectCpuTime(result: ObservableLongMeasurement) {
val counters = hypervisor.counters
- val grantedWork = counters.actual
- val overcommittedWork = counters.overcommit
- val interferedWork = (counters as? SimResourceDistributorMaxMin.Counters)?.interference ?: 0.0
-
- _totalTime += (duration / 1000.0) * coreCount
- val activeTime = (grantedWork * d).roundToLong()
- val idleTime = (_totalTime - grantedWork * d).roundToLong()
- val stealTime = (overcommittedWork * d).roundToLong()
- val lostTime = (interferedWork * d).roundToLong()
-
- result.observe(activeTime, _activeState)
- result.observe(idleTime, _idleState)
- result.observe(stealTime, _stealState)
- result.observe(lostTime, _lostState)
-
- for (guest in guests.values) {
- guest.collectCpuTime(duration, result)
- }
- }
- private var _lastPowerCallback = clock.millis()
- private var _totalPower = 0.0
+ result.observe(counters.cpuActiveTime / 1000L, _activeState)
+ result.observe(counters.cpuIdleTime / 1000L, _idleState)
+ result.observe(counters.cpuStealTime / 1000L, _stealState)
+ result.observe(counters.cpuLostTime / 1000L, _lostState)
- /**
- * Helper function to collect the total power usage of the machine.
- */
- private fun collectPowerTotal(result: ObservableDoubleMeasurement) {
- val now = clock.millis()
- val duration = now - _lastPowerCallback
-
- _totalPower += duration / 1000.0 * machine.powerDraw
- result.observe(_totalPower)
-
- _lastPowerCallback = now
+ val guests = _guests
+ for (i in guests.indices) {
+ guests[i].collectCpuTime(result)
+ }
}
private var _lastReport = clock.millis()
@@ -457,14 +423,21 @@ public class SimHost(
/**
* Helper function to track the uptime of a machine.
*/
- private fun collectTime(result: ObservableLongMeasurement? = null) {
+ private fun updateUptime() {
val now = clock.millis()
val duration = now - _lastReport
+ _lastReport = now
- try {
- collectTime(duration, result)
- } finally {
- _lastReport = now
+ if (_state == HostState.UP) {
+ _uptime += duration
+ } else if (_state == HostState.DOWN && scope.isActive) {
+ // Only increment downtime if the machine is in a failure state
+ _downtime += duration
+ }
+
+ val guests = _guests
+ for (i in guests.indices) {
+ guests[i].updateUptime(duration)
}
}
@@ -476,19 +449,15 @@ public class SimHost(
/**
* Helper function to track the uptime of a machine.
*/
- private fun collectTime(duration: Long, result: ObservableLongMeasurement? = null) {
- if (state == HostState.UP) {
- _uptime += duration
- } else if (state == HostState.DOWN && scope.isActive) {
- // Only increment downtime if the machine is in a failure state
- _downtime += duration
- }
+ private fun collectUptime(result: ObservableLongMeasurement) {
+ updateUptime()
- result?.observe(_uptime, _upState)
- result?.observe(_downtime, _downState)
+ result.observe(_uptime, _upState)
+ result.observe(_downtime, _downState)
- for (guest in guests.values) {
- guest.collectUptime(duration, result)
+ val guests = _guests
+ for (i in guests.indices) {
+ guests[i].collectUptime(result)
}
}
@@ -497,13 +466,14 @@ public class SimHost(
/**
* Helper function to track the boot time of a machine.
*/
- private fun collectBootTime(result: ObservableLongMeasurement? = null) {
+ private fun collectBootTime(result: ObservableLongMeasurement) {
if (_bootTime != Long.MIN_VALUE) {
- result?.observe(_bootTime)
+ result.observe(_bootTime)
}
- for (guest in guests.values) {
- guest.collectBootTime(result)
+ val guests = _guests
+ for (i in guests.indices) {
+ guests[i].collectBootTime(result)
}
}
}
diff --git a/opendc-compute/opendc-compute-simulator/src/main/kotlin/org/opendc/compute/simulator/internal/Guest.kt b/opendc-compute/opendc-compute-simulator/src/main/kotlin/org/opendc/compute/simulator/internal/Guest.kt
index 7f33154a..5ea1860d 100644
--- a/opendc-compute/opendc-compute-simulator/src/main/kotlin/org/opendc/compute/simulator/internal/Guest.kt
+++ b/opendc-compute/opendc-compute-simulator/src/main/kotlin/org/opendc/compute/simulator/internal/Guest.kt
@@ -24,6 +24,7 @@ package org.opendc.compute.simulator.internal
import io.opentelemetry.api.common.AttributeKey
import io.opentelemetry.api.common.Attributes
+import io.opentelemetry.api.common.AttributesBuilder
import io.opentelemetry.api.metrics.ObservableDoubleMeasurement
import io.opentelemetry.api.metrics.ObservableLongMeasurement
import io.opentelemetry.semconv.resource.attributes.ResourceAttributes
@@ -33,12 +34,10 @@ import org.opendc.compute.api.Server
import org.opendc.compute.api.ServerState
import org.opendc.compute.simulator.SimHost
import org.opendc.compute.simulator.SimWorkloadMapper
-import org.opendc.simulator.compute.SimAbstractMachine
-import org.opendc.simulator.compute.SimMachine
+import org.opendc.simulator.compute.kernel.SimVirtualMachine
import org.opendc.simulator.compute.workload.SimWorkload
import java.time.Clock
import kotlin.coroutines.CoroutineContext
-import kotlin.math.roundToLong
/**
* A virtual machine instance that is managed by a [SimHost].
@@ -50,7 +49,7 @@ internal class Guest(
private val mapper: SimWorkloadMapper,
private val listener: GuestListener,
val server: Server,
- val machine: SimMachine
+ val machine: SimVirtualMachine
) {
/**
* The [CoroutineScope] of the guest.
@@ -73,17 +72,7 @@ internal class Guest(
/**
* The attributes of the guest.
*/
- val attributes: Attributes = Attributes.builder()
- .put(ResourceAttributes.HOST_NAME, server.name)
- .put(ResourceAttributes.HOST_ID, server.uid.toString())
- .put(ResourceAttributes.HOST_TYPE, server.flavor.name)
- .put(AttributeKey.longKey("host.num_cpus"), server.flavor.cpuCount.toLong())
- .put(AttributeKey.longKey("host.mem_capacity"), server.flavor.memorySize)
- .put(AttributeKey.stringArrayKey("host.labels"), server.labels.map { (k, v) -> "$k:$v" })
- .put(ResourceAttributes.HOST_ARCH, ResourceAttributes.HostArchValues.AMD64)
- .put(ResourceAttributes.HOST_IMAGE_NAME, server.image.name)
- .put(ResourceAttributes.HOST_IMAGE_ID, server.image.uid.toString())
- .build()
+ val attributes: Attributes = GuestAttributes(this)
/**
* Start the guest.
@@ -116,12 +105,12 @@ internal class Guest(
}
/**
- * Terminate the guest.
+ * Delete the guest.
*
* This operation will stop the guest if it is running on the host and remove all resources associated with the
* guest.
*/
- suspend fun terminate() {
+ suspend fun delete() {
stop()
state = ServerState.DELETED
@@ -198,7 +187,7 @@ internal class Guest(
}
/**
- * Run the process that models the virtual machine lifecycle as a coroutine.
+ * Converge the process that models the virtual machine lifecycle as a coroutine.
*/
private suspend fun runMachine(workload: SimWorkload) {
delay(1) // TODO Introduce model for boot time
@@ -226,27 +215,30 @@ internal class Guest(
private var _uptime = 0L
private var _downtime = 0L
- private val _upState = Attributes.builder()
- .putAll(attributes)
+ private val _upState = attributes.toBuilder()
.put(STATE_KEY, "up")
.build()
- private val _downState = Attributes.builder()
- .putAll(attributes)
+ private val _downState = attributes.toBuilder()
.put(STATE_KEY, "down")
.build()
/**
- * Helper function to track the uptime of the guest.
+ * Helper function to track the uptime and downtime of the guest.
*/
- fun collectUptime(duration: Long, result: ObservableLongMeasurement? = null) {
+ fun updateUptime(duration: Long) {
if (state == ServerState.RUNNING) {
_uptime += duration
} else if (state == ServerState.ERROR) {
_downtime += duration
}
+ }
- result?.observe(_uptime, _upState)
- result?.observe(_downtime, _downState)
+ /**
+ * Helper function to track the uptime of the guest.
+ */
+ fun collectUptime(result: ObservableLongMeasurement) {
+ result.observe(_uptime, _upState)
+ result.observe(_downtime, _downState)
}
private var _bootTime = Long.MIN_VALUE
@@ -254,55 +246,35 @@ internal class Guest(
/**
* Helper function to track the boot time of the guest.
*/
- fun collectBootTime(result: ObservableLongMeasurement? = null) {
+ fun collectBootTime(result: ObservableLongMeasurement) {
if (_bootTime != Long.MIN_VALUE) {
- result?.observe(_bootTime)
+ result.observe(_bootTime)
}
}
- private val _activeState = Attributes.builder()
- .putAll(attributes)
+ private val _activeState = attributes.toBuilder()
.put(STATE_KEY, "active")
.build()
- private val _stealState = Attributes.builder()
- .putAll(attributes)
+ private val _stealState = attributes.toBuilder()
.put(STATE_KEY, "steal")
.build()
- private val _lostState = Attributes.builder()
- .putAll(attributes)
+ private val _lostState = attributes.toBuilder()
.put(STATE_KEY, "lost")
.build()
- private val _idleState = Attributes.builder()
- .putAll(attributes)
+ private val _idleState = attributes.toBuilder()
.put(STATE_KEY, "idle")
.build()
- private var _totalTime = 0.0
/**
* Helper function to track the CPU time of a machine.
*/
- fun collectCpuTime(duration: Long, result: ObservableLongMeasurement) {
- val coreCount = server.flavor.cpuCount
- val d = coreCount / _cpuLimit
-
- var grantedWork = 0.0
- var overcommittedWork = 0.0
+ fun collectCpuTime(result: ObservableLongMeasurement) {
+ val counters = machine.counters
- for (cpu in (machine as SimAbstractMachine).cpus) {
- val counters = cpu.counters
- grantedWork += counters.actual
- overcommittedWork += counters.overcommit
- }
-
- _totalTime += (duration / 1000.0) * coreCount
- val activeTime = (grantedWork * d).roundToLong()
- val idleTime = (_totalTime - grantedWork * d).roundToLong()
- val stealTime = (overcommittedWork * d).roundToLong()
-
- result.observe(activeTime, _activeState)
- result.observe(idleTime, _idleState)
- result.observe(stealTime, _stealState)
- result.observe(0, _lostState)
+ result.observe(counters.cpuActiveTime / 1000, _activeState)
+ result.observe(counters.cpuIdleTime / 1000, _idleState)
+ result.observe(counters.cpuStealTime / 1000, _stealState)
+ result.observe(counters.cpuLostTime / 1000, _lostState)
}
private val _cpuLimit = machine.model.cpus.sumOf { it.frequency }
@@ -313,4 +285,66 @@ internal class Guest(
fun collectCpuLimit(result: ObservableDoubleMeasurement) {
result.observe(_cpuLimit, attributes)
}
+
+ /**
+ * An optimized [Attributes] implementation.
+ */
+ private class GuestAttributes(private val uid: String, private val attributes: Attributes) : Attributes by attributes {
+ /**
+ * Construct a [GuestAttributes] instance from a [Guest].
+ */
+ constructor(guest: Guest) : this(
+ guest.server.uid.toString(),
+ Attributes.builder()
+ .put(ResourceAttributes.HOST_NAME, guest.server.name)
+ .put(ResourceAttributes.HOST_ID, guest.server.uid.toString())
+ .put(ResourceAttributes.HOST_TYPE, guest.server.flavor.name)
+ .put(AttributeKey.longKey("host.num_cpus"), guest.server.flavor.cpuCount.toLong())
+ .put(AttributeKey.longKey("host.mem_capacity"), guest.server.flavor.memorySize)
+ .put(AttributeKey.stringArrayKey("host.labels"), guest.server.labels.map { (k, v) -> "$k:$v" })
+ .put(ResourceAttributes.HOST_ARCH, ResourceAttributes.HostArchValues.AMD64)
+ .put(ResourceAttributes.HOST_IMAGE_NAME, guest.server.image.name)
+ .put(ResourceAttributes.HOST_IMAGE_ID, guest.server.image.uid.toString())
+ .build()
+ )
+
+ override fun <T : Any?> get(key: AttributeKey<T>): T? {
+ // Optimize access to the HOST_ID key which is accessed quite often
+ if (key == ResourceAttributes.HOST_ID) {
+ @Suppress("UNCHECKED_CAST")
+ return uid as T?
+ }
+ return attributes.get(key)
+ }
+
+ override fun toBuilder(): AttributesBuilder {
+ val delegate = attributes.toBuilder()
+ return object : AttributesBuilder {
+
+ override fun putAll(attributes: Attributes): AttributesBuilder {
+ delegate.putAll(attributes)
+ return this
+ }
+
+ override fun <T : Any?> put(key: AttributeKey<Long>, value: Int): AttributesBuilder {
+ delegate.put<T>(key, value)
+ return this
+ }
+
+ override fun <T : Any?> put(key: AttributeKey<T>, value: T): AttributesBuilder {
+ delegate.put(key, value)
+ return this
+ }
+
+ override fun build(): Attributes = GuestAttributes(uid, delegate.build())
+ }
+ }
+
+ override fun equals(other: Any?): Boolean = attributes == other
+
+ // Cache hash code
+ private val _hash = attributes.hashCode()
+
+ override fun hashCode(): Int = _hash
+ }
}
diff --git a/opendc-compute/opendc-compute-simulator/src/main/kotlin/org/opendc/compute/simulator/internal/HostFaultInjectorImpl.kt b/opendc-compute/opendc-compute-simulator/src/main/kotlin/org/opendc/compute/simulator/internal/HostFaultInjectorImpl.kt
index 6919b7fd..7d46e626 100644
--- a/opendc-compute/opendc-compute-simulator/src/main/kotlin/org/opendc/compute/simulator/internal/HostFaultInjectorImpl.kt
+++ b/opendc-compute/opendc-compute-simulator/src/main/kotlin/org/opendc/compute/simulator/internal/HostFaultInjectorImpl.kt
@@ -75,7 +75,7 @@ internal class HostFaultInjectorImpl(
}
/**
- * Run the injection process.
+ * Converge the injection process.
*/
private suspend fun runInjector() {
while (true) {
diff --git a/opendc-compute/opendc-compute-simulator/src/test/kotlin/org/opendc/compute/simulator/SimHostTest.kt b/opendc-compute/opendc-compute-simulator/src/test/kotlin/org/opendc/compute/simulator/SimHostTest.kt
index e75c31a0..26089b6d 100644
--- a/opendc-compute/opendc-compute-simulator/src/test/kotlin/org/opendc/compute/simulator/SimHostTest.kt
+++ b/opendc-compute/opendc-compute-simulator/src/test/kotlin/org/opendc/compute/simulator/SimHostTest.kt
@@ -41,7 +41,7 @@ import org.opendc.simulator.compute.model.ProcessingNode
import org.opendc.simulator.compute.model.ProcessingUnit
import org.opendc.simulator.compute.workload.SimTraceWorkload
import org.opendc.simulator.core.runBlockingSimulation
-import org.opendc.simulator.resources.SimResourceInterpreter
+import org.opendc.simulator.flow.FlowEngine
import org.opendc.telemetry.compute.ComputeMetricExporter
import org.opendc.telemetry.compute.HOST_ID
import org.opendc.telemetry.compute.table.HostData
@@ -87,14 +87,14 @@ internal class SimHostTest {
.setClock(clock.toOtelClock())
.build()
- val interpreter = SimResourceInterpreter(coroutineContext, clock)
+ val engine = FlowEngine(coroutineContext, clock)
val virtDriver = SimHost(
uid = hostId,
name = "test",
model = machineModel,
meta = emptyMap(),
coroutineContext,
- interpreter,
+ engine,
meterProvider,
SimFairShareHypervisorProvider()
)
@@ -170,9 +170,9 @@ internal class SimHostTest {
reader.close()
assertAll(
- { assertEquals(659, activeTime, "Active time does not match") },
- { assertEquals(2342, idleTime, "Idle time does not match") },
- { assertEquals(638, stealTime, "Steal time does not match") },
+ { assertEquals(658, activeTime, "Active time does not match") },
+ { assertEquals(1741, idleTime, "Idle time does not match") },
+ { assertEquals(637, stealTime, "Steal time does not match") },
{ assertEquals(1500001, clock.millis()) }
)
}
@@ -199,14 +199,14 @@ internal class SimHostTest {
.setClock(clock.toOtelClock())
.build()
- val interpreter = SimResourceInterpreter(coroutineContext, clock)
+ val engine = FlowEngine(coroutineContext, clock)
val host = SimHost(
uid = hostId,
name = "test",
model = machineModel,
meta = emptyMap(),
coroutineContext,
- interpreter,
+ engine,
meterProvider,
SimFairShareHypervisorProvider()
)
@@ -253,7 +253,7 @@ internal class SimHostTest {
host.spawn(server)
delay(5000L)
host.fail()
- delay(5000L)
+ delay(duration * 1000)
host.recover()
suspendCancellableCoroutine<Unit> { cont ->
@@ -274,12 +274,12 @@ internal class SimHostTest {
reader.close()
assertAll(
- { assertEquals(2661, idleTime, "Idle time does not match") },
- { assertEquals(339, activeTime, "Active time does not match") },
- { assertEquals(1195001, uptime, "Uptime does not match") },
- { assertEquals(5000, downtime, "Downtime does not match") },
- { assertEquals(1195000, guestUptime, "Guest uptime does not match") },
- { assertEquals(5000, guestDowntime, "Guest downtime does not match") },
+ { assertEquals(1175, idleTime, "Idle time does not match") },
+ { assertEquals(624, activeTime, "Active time does not match") },
+ { assertEquals(900001, uptime, "Uptime does not match") },
+ { assertEquals(300000, downtime, "Downtime does not match") },
+ { assertEquals(900000, guestUptime, "Guest uptime does not match") },
+ { assertEquals(300000, guestDowntime, "Guest downtime does not match") },
)
}
diff --git a/opendc-compute/opendc-compute-workload/src/main/kotlin/org/opendc/compute/workload/ComputeWorkloadRunner.kt b/opendc-compute/opendc-compute-workload/src/main/kotlin/org/opendc/compute/workload/ComputeWorkloadRunner.kt
index ed45bd8a..283f82fe 100644
--- a/opendc-compute/opendc-compute-workload/src/main/kotlin/org/opendc/compute/workload/ComputeWorkloadRunner.kt
+++ b/opendc-compute/opendc-compute-workload/src/main/kotlin/org/opendc/compute/workload/ComputeWorkloadRunner.kt
@@ -36,7 +36,7 @@ import org.opendc.compute.simulator.SimHost
import org.opendc.compute.workload.topology.HostSpec
import org.opendc.simulator.compute.kernel.interference.VmInterferenceModel
import org.opendc.simulator.compute.workload.SimTraceWorkload
-import org.opendc.simulator.resources.SimResourceInterpreter
+import org.opendc.simulator.flow.FlowEngine
import org.opendc.telemetry.compute.*
import org.opendc.telemetry.sdk.toOtelClock
import java.time.Clock
@@ -73,9 +73,9 @@ public class ComputeWorkloadRunner(
private val _metricProducers = mutableListOf<MetricProducer>()
/**
- * The [SimResourceInterpreter] to simulate the hosts.
+ * The [FlowEngine] to simulate the hosts.
*/
- private val interpreter = SimResourceInterpreter(context, clock)
+ private val engine = FlowEngine(context, clock)
/**
* The hosts that belong to this class.
@@ -89,7 +89,7 @@ public class ComputeWorkloadRunner(
}
/**
- * Run a simulation of the [ComputeService] by replaying the workload trace given by [trace].
+ * Converge a simulation of the [ComputeService] by replaying the workload trace given by [trace].
*/
public suspend fun run(trace: List<VirtualMachine>, seed: Long) {
val random = Random(seed)
@@ -178,7 +178,7 @@ public class ComputeWorkloadRunner(
spec.model,
spec.meta,
context,
- interpreter,
+ engine,
meterProvider,
spec.hypervisor,
powerDriver = spec.powerDriver,
diff --git a/opendc-experiments/opendc-experiments-capelin/src/main/kotlin/org/opendc/experiments/capelin/Portfolio.kt b/opendc-experiments/opendc-experiments-capelin/src/main/kotlin/org/opendc/experiments/capelin/Portfolio.kt
index 21ff3ab0..4e855f82 100644
--- a/opendc-experiments/opendc-experiments-capelin/src/main/kotlin/org/opendc/experiments/capelin/Portfolio.kt
+++ b/opendc-experiments/opendc-experiments-capelin/src/main/kotlin/org/opendc/experiments/capelin/Portfolio.kt
@@ -131,7 +131,7 @@ abstract class Portfolio(name: String) : Experiment(name) {
// Instantiate the desired topology
runner.apply(topology)
- // Run the workload trace
+ // Converge the workload trace
runner.run(workload.source.resolve(workloadLoader, seeder), seeder.nextLong())
} finally {
runner.close()
diff --git a/opendc-experiments/opendc-experiments-capelin/src/test/kotlin/org/opendc/experiments/capelin/CapelinIntegrationTest.kt b/opendc-experiments/opendc-experiments-capelin/src/test/kotlin/org/opendc/experiments/capelin/CapelinIntegrationTest.kt
index 30cc1466..9d540118 100644
--- a/opendc-experiments/opendc-experiments-capelin/src/test/kotlin/org/opendc/experiments/capelin/CapelinIntegrationTest.kt
+++ b/opendc-experiments/opendc-experiments-capelin/src/test/kotlin/org/opendc/experiments/capelin/CapelinIntegrationTest.kt
@@ -116,11 +116,11 @@ class CapelinIntegrationTest {
{ assertEquals(0, serviceMetrics.serversActive, "All VMs should finish after a run") },
{ assertEquals(0, serviceMetrics.attemptsFailure, "No VM should be unscheduled") },
{ assertEquals(0, serviceMetrics.serversPending, "No VM should not be in the queue") },
- { assertEquals(223331032, this@CapelinIntegrationTest.exporter.idleTime) { "Incorrect idle time" } },
- { assertEquals(67006568, this@CapelinIntegrationTest.exporter.activeTime) { "Incorrect active time" } },
- { assertEquals(3159379, this@CapelinIntegrationTest.exporter.stealTime) { "Incorrect steal time" } },
+ { assertEquals(223325655, this@CapelinIntegrationTest.exporter.idleTime) { "Incorrect idle time" } },
+ { assertEquals(67006560, this@CapelinIntegrationTest.exporter.activeTime) { "Incorrect active time" } },
+ { assertEquals(3159377, this@CapelinIntegrationTest.exporter.stealTime) { "Incorrect steal time" } },
{ assertEquals(0, this@CapelinIntegrationTest.exporter.lostTime) { "Incorrect lost time" } },
- { assertEquals(5.841120890240688E9, this@CapelinIntegrationTest.exporter.energyUsage, 0.01) { "Incorrect power draw" } },
+ { assertEquals(5.840207707767459E9, this@CapelinIntegrationTest.exporter.energyUsage, 0.01) { "Incorrect power draw" } },
)
}
@@ -160,10 +160,11 @@ class CapelinIntegrationTest {
// Note that these values have been verified beforehand
assertAll(
- { assertEquals(10998110, this@CapelinIntegrationTest.exporter.idleTime) { "Idle time incorrect" } },
- { assertEquals(9740290, this@CapelinIntegrationTest.exporter.activeTime) { "Active time incorrect" } },
+ { assertEquals(10997726, this@CapelinIntegrationTest.exporter.idleTime) { "Idle time incorrect" } },
+ { assertEquals(9740289, this@CapelinIntegrationTest.exporter.activeTime) { "Active time incorrect" } },
{ assertEquals(0, this@CapelinIntegrationTest.exporter.stealTime) { "Steal time incorrect" } },
- { assertEquals(0, this@CapelinIntegrationTest.exporter.lostTime) { "Lost time incorrect" } }
+ { assertEquals(0, this@CapelinIntegrationTest.exporter.lostTime) { "Lost time incorrect" } },
+ { assertEquals(7.009945802750012E8, this@CapelinIntegrationTest.exporter.energyUsage, 0.01) { "Incorrect power draw" } }
)
}
@@ -209,10 +210,10 @@ class CapelinIntegrationTest {
// Note that these values have been verified beforehand
assertAll(
- { assertEquals(6013899, this@CapelinIntegrationTest.exporter.idleTime) { "Idle time incorrect" } },
- { assertEquals(14724501, this@CapelinIntegrationTest.exporter.activeTime) { "Active time incorrect" } },
+ { assertEquals(6013515, this@CapelinIntegrationTest.exporter.idleTime) { "Idle time incorrect" } },
+ { assertEquals(14724500, this@CapelinIntegrationTest.exporter.activeTime) { "Active time incorrect" } },
{ assertEquals(12530742, this@CapelinIntegrationTest.exporter.stealTime) { "Steal time incorrect" } },
- { assertEquals(473394, this@CapelinIntegrationTest.exporter.lostTime) { "Lost time incorrect" } }
+ { assertEquals(480866, this@CapelinIntegrationTest.exporter.lostTime) { "Lost time incorrect" } }
)
}
@@ -252,8 +253,8 @@ class CapelinIntegrationTest {
// Note that these values have been verified beforehand
assertAll(
- { assertEquals(11134319, exporter.idleTime) { "Idle time incorrect" } },
- { assertEquals(9604081, exporter.activeTime) { "Active time incorrect" } },
+ { assertEquals(10865478, exporter.idleTime) { "Idle time incorrect" } },
+ { assertEquals(9606177, exporter.activeTime) { "Active time incorrect" } },
{ assertEquals(0, exporter.stealTime) { "Steal time incorrect" } },
{ assertEquals(0, exporter.lostTime) { "Lost time incorrect" } },
{ assertEquals(2559005056, exporter.uptime) { "Uptime incorrect" } }
diff --git a/opendc-experiments/opendc-experiments-tf20/src/main/kotlin/org/opendc/experiments/tf20/core/SimTFDevice.kt b/opendc-experiments/opendc-experiments-tf20/src/main/kotlin/org/opendc/experiments/tf20/core/SimTFDevice.kt
index 0873aac9..fb36d2c7 100644
--- a/opendc-experiments/opendc-experiments-tf20/src/main/kotlin/org/opendc/experiments/tf20/core/SimTFDevice.kt
+++ b/opendc-experiments/opendc-experiments-tf20/src/main/kotlin/org/opendc/experiments/tf20/core/SimTFDevice.kt
@@ -35,12 +35,13 @@ import org.opendc.simulator.compute.model.ProcessingUnit
import org.opendc.simulator.compute.power.PowerModel
import org.opendc.simulator.compute.power.SimplePowerDriver
import org.opendc.simulator.compute.workload.SimWorkload
-import org.opendc.simulator.resources.*
+import org.opendc.simulator.flow.*
import java.time.Clock
import java.util.*
import kotlin.coroutines.Continuation
import kotlin.coroutines.CoroutineContext
import kotlin.coroutines.resume
+import kotlin.math.roundToLong
/**
* A [TFDevice] implementation using simulated components.
@@ -64,7 +65,7 @@ public class SimTFDevice(
* The [SimMachine] representing the device.
*/
private val machine = SimBareMetalMachine(
- SimResourceInterpreter(scope.coroutineContext, clock), MachineModel(listOf(pu), listOf(memory)),
+ FlowEngine(scope.coroutineContext, clock), MachineModel(listOf(pu), listOf(memory)),
SimplePowerDriver(powerModel)
)
@@ -94,11 +95,11 @@ public class SimTFDevice(
/**
* The workload that will be run by the device.
*/
- private val workload = object : SimWorkload, SimResourceConsumer {
+ private val workload = object : SimWorkload, FlowSource {
/**
* The resource context to interrupt the workload with.
*/
- var ctx: SimResourceContext? = null
+ var ctx: FlowConnection? = null
/**
* The capacity of the device.
@@ -127,13 +128,26 @@ public class SimTFDevice(
}
}
- override fun onNext(ctx: SimResourceContext): SimResourceCommand {
+ override fun onStart(conn: FlowConnection, now: Long) {
+ ctx = conn
+ capacity = conn.capacity
+
+ conn.shouldSourceConverge = true
+ }
+
+ override fun onPull(conn: FlowConnection, now: Long, delta: Long): Long {
+ val consumedWork = conn.rate * delta / 1000.0
+
+ capacity = conn.capacity
+
val activeWork = activeWork
if (activeWork != null) {
- if (activeWork.consume(activeWork.flops - ctx.remainingWork)) {
+ if (activeWork.consume(consumedWork)) {
this.activeWork = null
} else {
- return SimResourceCommand.Consume(activeWork.flops, ctx.capacity)
+ val duration = (activeWork.flops / conn.capacity * 1000).roundToLong()
+ conn.push(conn.capacity)
+ return duration
}
}
@@ -141,28 +155,18 @@ public class SimTFDevice(
val head = queue.poll()
return if (head != null) {
this.activeWork = head
- SimResourceCommand.Consume(head.flops, ctx.capacity)
+ val duration = (head.flops / conn.capacity * 1000).roundToLong()
+ conn.push(conn.capacity)
+ duration
} else {
- SimResourceCommand.Idle()
+ conn.push(0.0)
+ Long.MAX_VALUE
}
}
- override fun onEvent(ctx: SimResourceContext, event: SimResourceEvent) {
- when (event) {
- SimResourceEvent.Start -> {
- this.ctx = ctx
- this.capacity = ctx.capacity
- }
- SimResourceEvent.Capacity -> {
- this.capacity = ctx.capacity
- ctx.interrupt()
- }
- SimResourceEvent.Run -> {
- _usage.record(ctx.speed)
- _power.record(machine.psu.powerDraw)
- }
- else -> {}
- }
+ override fun onConverge(conn: FlowConnection, now: Long, delta: Long) {
+ _usage.record(conn.rate)
+ _power.record(machine.psu.powerDraw)
}
}
@@ -180,7 +184,7 @@ public class SimTFDevice(
override suspend fun compute(flops: Double) = suspendCancellableCoroutine<Unit> { cont ->
workload.queue.add(Work(flops, cont))
if (workload.isIdle) {
- workload.ctx?.interrupt()
+ workload.ctx?.pull()
}
}
diff --git a/opendc-experiments/opendc-experiments-tf20/src/main/kotlin/org/opendc/experiments/tf20/distribute/Strategy.kt b/opendc-experiments/opendc-experiments-tf20/src/main/kotlin/org/opendc/experiments/tf20/distribute/Strategy.kt
index 5839c0df..3e755b56 100644
--- a/opendc-experiments/opendc-experiments-tf20/src/main/kotlin/org/opendc/experiments/tf20/distribute/Strategy.kt
+++ b/opendc-experiments/opendc-experiments-tf20/src/main/kotlin/org/opendc/experiments/tf20/distribute/Strategy.kt
@@ -27,7 +27,7 @@ package org.opendc.experiments.tf20.distribute
*/
public interface Strategy {
/**
- * Run the specified batch using the given strategy.
+ * Converge the specified batch using the given strategy.
*/
public suspend fun run(forward: Double, backward: Double, batchSize: Int)
}
diff --git a/opendc-faas/opendc-faas-simulator/src/main/kotlin/org/opendc/faas/simulator/SimFunctionDeployer.kt b/opendc-faas/opendc-faas-simulator/src/main/kotlin/org/opendc/faas/simulator/SimFunctionDeployer.kt
index 68bdc337..020d75b5 100644
--- a/opendc-faas/opendc-faas-simulator/src/main/kotlin/org/opendc/faas/simulator/SimFunctionDeployer.kt
+++ b/opendc-faas/opendc-faas-simulator/src/main/kotlin/org/opendc/faas/simulator/SimFunctionDeployer.kt
@@ -36,7 +36,7 @@ import org.opendc.simulator.compute.SimMachine
import org.opendc.simulator.compute.model.MachineModel
import org.opendc.simulator.compute.power.ConstantPowerModel
import org.opendc.simulator.compute.power.SimplePowerDriver
-import org.opendc.simulator.resources.SimResourceInterpreter
+import org.opendc.simulator.flow.FlowEngine
import java.time.Clock
import java.util.ArrayDeque
import kotlin.coroutines.Continuation
@@ -74,7 +74,7 @@ public class SimFunctionDeployer(
* The machine that will execute the workloads.
*/
public val machine: SimMachine = SimBareMetalMachine(
- SimResourceInterpreter(scope.coroutineContext, clock),
+ FlowEngine(scope.coroutineContext, clock),
model,
SimplePowerDriver(ConstantPowerModel(0.0))
)
diff --git a/opendc-simulator/opendc-simulator-compute/build.gradle.kts b/opendc-simulator/opendc-simulator-compute/build.gradle.kts
index 7d06ee62..a2bb89c2 100644
--- a/opendc-simulator/opendc-simulator-compute/build.gradle.kts
+++ b/opendc-simulator/opendc-simulator-compute/build.gradle.kts
@@ -31,9 +31,11 @@ plugins {
dependencies {
api(platform(projects.opendcPlatform))
- api(projects.opendcSimulator.opendcSimulatorResources)
+ api(projects.opendcSimulator.opendcSimulatorFlow)
api(projects.opendcSimulator.opendcSimulatorPower)
api(projects.opendcSimulator.opendcSimulatorNetwork)
implementation(projects.opendcSimulator.opendcSimulatorCore)
implementation(projects.opendcUtils)
+
+ testImplementation(libs.slf4j.simple)
}
diff --git a/opendc-simulator/opendc-simulator-compute/src/jmh/kotlin/org/opendc/simulator/compute/SimMachineBenchmarks.kt b/opendc-simulator/opendc-simulator-compute/src/jmh/kotlin/org/opendc/simulator/compute/SimMachineBenchmarks.kt
index 30797089..d654d58a 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
@@ -36,8 +36,9 @@ import org.opendc.simulator.compute.power.SimplePowerDriver
import org.opendc.simulator.compute.workload.SimTraceWorkload
import org.opendc.simulator.core.SimulationCoroutineScope
import org.opendc.simulator.core.runBlockingSimulation
-import org.opendc.simulator.resources.SimResourceInterpreter
+import org.opendc.simulator.flow.FlowEngine
import org.openjdk.jmh.annotations.*
+import java.util.concurrent.ThreadLocalRandom
import java.util.concurrent.TimeUnit
@State(Scope.Thread)
@@ -47,13 +48,13 @@ import java.util.concurrent.TimeUnit
@OptIn(ExperimentalCoroutinesApi::class)
class SimMachineBenchmarks {
private lateinit var scope: SimulationCoroutineScope
- private lateinit var interpreter: SimResourceInterpreter
+ private lateinit var engine: FlowEngine
private lateinit var machineModel: MachineModel
@Setup
fun setUp() {
scope = SimulationCoroutineScope()
- interpreter = SimResourceInterpreter(scope.coroutineContext, scope.clock)
+ engine = FlowEngine(scope.coroutineContext, scope.clock)
val cpuNode = ProcessingNode("Intel", "Xeon", "amd64", 2)
@@ -63,22 +64,15 @@ class SimMachineBenchmarks {
)
}
- @State(Scope.Benchmark)
+ @State(Scope.Thread)
class Workload {
lateinit var trace: Sequence<SimTraceWorkload.Fragment>
@Setup
fun setUp() {
- trace = sequenceOf(
- SimTraceWorkload.Fragment(0, 1000, 28.0, 1),
- SimTraceWorkload.Fragment(1000, 1000, 3500.0, 1),
- SimTraceWorkload.Fragment(2000, 1000, 0.0, 1),
- SimTraceWorkload.Fragment(3000, 1000, 183.0, 1),
- SimTraceWorkload.Fragment(4000, 1000, 400.0, 1),
- SimTraceWorkload.Fragment(5000, 1000, 100.0, 1),
- SimTraceWorkload.Fragment(6000, 1000, 3000.0, 1),
- SimTraceWorkload.Fragment(7000, 1000, 4500.0, 1),
- )
+ val random = ThreadLocalRandom.current()
+ val entries = List(10000) { SimTraceWorkload.Fragment(it * 1000L, 1000, random.nextDouble(0.0, 4500.0), 1) }
+ trace = entries.asSequence()
}
}
@@ -86,7 +80,7 @@ class SimMachineBenchmarks {
fun benchmarkBareMetal(state: Workload) {
return scope.runBlockingSimulation {
val machine = SimBareMetalMachine(
- interpreter, machineModel, SimplePowerDriver(ConstantPowerModel(0.0))
+ engine, machineModel, SimplePowerDriver(ConstantPowerModel(0.0))
)
return@runBlockingSimulation machine.run(SimTraceWorkload(state.trace))
}
@@ -96,9 +90,9 @@ class SimMachineBenchmarks {
fun benchmarkSpaceSharedHypervisor(state: Workload) {
return scope.runBlockingSimulation {
val machine = SimBareMetalMachine(
- interpreter, machineModel, SimplePowerDriver(ConstantPowerModel(0.0))
+ engine, machineModel, SimplePowerDriver(ConstantPowerModel(0.0))
)
- val hypervisor = SimSpaceSharedHypervisor(interpreter)
+ val hypervisor = SimSpaceSharedHypervisor(engine, null, null)
launch { machine.run(hypervisor) }
@@ -117,9 +111,9 @@ class SimMachineBenchmarks {
fun benchmarkFairShareHypervisorSingle(state: Workload) {
return scope.runBlockingSimulation {
val machine = SimBareMetalMachine(
- interpreter, machineModel, SimplePowerDriver(ConstantPowerModel(0.0))
+ engine, machineModel, SimplePowerDriver(ConstantPowerModel(0.0))
)
- val hypervisor = SimFairShareHypervisor(interpreter)
+ val hypervisor = SimFairShareHypervisor(engine, null, null, null)
launch { machine.run(hypervisor) }
@@ -138,9 +132,9 @@ class SimMachineBenchmarks {
fun benchmarkFairShareHypervisorDouble(state: Workload) {
return scope.runBlockingSimulation {
val machine = SimBareMetalMachine(
- interpreter, machineModel, SimplePowerDriver(ConstantPowerModel(0.0))
+ engine, machineModel, SimplePowerDriver(ConstantPowerModel(0.0))
)
- val hypervisor = SimFairShareHypervisor(interpreter)
+ val hypervisor = SimFairShareHypervisor(engine, null, null, null)
launch { machine.run(hypervisor) }
diff --git a/opendc-simulator/opendc-simulator-compute/src/main/kotlin/org/opendc/simulator/compute/SimAbstractMachine.kt b/opendc-simulator/opendc-simulator-compute/src/main/kotlin/org/opendc/simulator/compute/SimAbstractMachine.kt
index f9db048d..60a10f20 100644
--- a/opendc-simulator/opendc-simulator-compute/src/main/kotlin/org/opendc/simulator/compute/SimAbstractMachine.kt
+++ b/opendc-simulator/opendc-simulator-compute/src/main/kotlin/org/opendc/simulator/compute/SimAbstractMachine.kt
@@ -30,22 +30,22 @@ import org.opendc.simulator.compute.model.MemoryUnit
import org.opendc.simulator.compute.model.NetworkAdapter
import org.opendc.simulator.compute.model.StorageDevice
import org.opendc.simulator.compute.workload.SimWorkload
-import org.opendc.simulator.resources.*
+import org.opendc.simulator.flow.*
import kotlin.coroutines.Continuation
import kotlin.coroutines.resume
/**
* Abstract implementation of the [SimMachine] interface.
*
- * @param interpreter The interpreter to manage the machine's resources.
+ * @param engine The engine to manage the machine's resources.
* @param parent The parent simulation system.
* @param model The model of the machine.
*/
public abstract class SimAbstractMachine(
- protected val interpreter: SimResourceInterpreter,
- final override val parent: SimResourceSystem?,
+ protected val engine: FlowEngine,
+ private val parent: FlowConvergenceListener?,
final override val model: MachineModel
-) : SimMachine, SimResourceSystem {
+) : SimMachine, FlowConvergenceListener {
/**
* The resources allocated for this machine.
*/
@@ -54,17 +54,17 @@ public abstract class SimAbstractMachine(
/**
* The memory interface of the machine.
*/
- public val memory: SimMemory = Memory(SimResourceSource(model.memory.sumOf { it.size }.toDouble(), interpreter), model.memory)
+ 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(adapter, i) }
+ 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(interpreter, device, i) }
+ public val storage: List<SimStorageInterface> = model.storage.mapIndexed { i, device -> StorageDeviceImpl(engine, device, i) }
/**
* The peripherals of the machine.
@@ -82,7 +82,7 @@ public abstract class SimAbstractMachine(
private var cont: Continuation<Unit>? = null
/**
- * Run the specified [SimWorkload] on this machine and suspend execution util the workload has finished.
+ * Converge the specified [SimWorkload] on this machine and suspend execution util the workload has finished.
*/
override suspend fun run(workload: SimWorkload, meta: Map<String, Any>) {
check(!isTerminated) { "Machine is terminated" }
@@ -96,14 +96,14 @@ public abstract class SimAbstractMachine(
// Cancel all cpus on cancellation
cont.invokeOnCancellation {
this.cont = null
- interpreter.batch {
+ engine.batch {
for (cpu in cpus) {
cpu.cancel()
}
}
}
- interpreter.batch { workload.onStart(ctx) }
+ engine.batch { workload.onStart(ctx) }
}
}
@@ -116,11 +116,15 @@ public abstract class SimAbstractMachine(
cancel()
}
+ override fun onConverge(now: Long, delta: Long) {
+ parent?.onConverge(now, delta)
+ }
+
/**
* Cancel the workload that is currently running on the machine.
*/
private fun cancel() {
- interpreter.batch {
+ engine.batch {
for (cpu in cpus) {
cpu.cancel()
}
@@ -137,8 +141,8 @@ public abstract class SimAbstractMachine(
* The execution context in which the workload runs.
*/
private inner class Context(override val meta: Map<String, Any>) : SimMachineContext {
- override val interpreter: SimResourceInterpreter
- get() = this@SimAbstractMachine.interpreter
+ override val engine: FlowEngine
+ get() = this@SimAbstractMachine.engine
override val cpus: List<SimProcessingUnit> = this@SimAbstractMachine.cpus
@@ -154,7 +158,7 @@ public abstract class SimAbstractMachine(
/**
* The [SimMemory] implementation for a machine.
*/
- private class Memory(source: SimResourceSource, override val models: List<MemoryUnit>) : SimMemory, SimResourceProvider by source {
+ private class Memory(source: FlowSink, override val models: List<MemoryUnit>) : SimMemory, FlowConsumer by source {
override fun toString(): String = "SimAbstractMachine.Memory"
}
@@ -162,6 +166,7 @@ public abstract class SimAbstractMachine(
* The [SimNetworkAdapter] implementation for a machine.
*/
private class NetworkAdapterImpl(
+ private val engine: FlowEngine,
model: NetworkAdapter,
index: Int
) : SimNetworkAdapter(), SimNetworkInterface {
@@ -169,18 +174,18 @@ public abstract class SimAbstractMachine(
override val bandwidth: Double = model.bandwidth
- override val provider: SimResourceProvider
+ override val provider: FlowConsumer
get() = _rx
- override fun createConsumer(): SimResourceConsumer = _tx
+ override fun createConsumer(): FlowSource = _tx
- override val tx: SimResourceProvider
+ override val tx: FlowConsumer
get() = _tx
- private val _tx = SimResourceForwarder()
+ private val _tx = FlowForwarder(engine)
- override val rx: SimResourceConsumer
+ override val rx: FlowSource
get() = _rx
- private val _rx = SimResourceForwarder()
+ private val _rx = FlowForwarder(engine)
override fun toString(): String = "SimAbstractMachine.NetworkAdapterImpl[name=$name,bandwidth=$bandwidth]"
}
@@ -189,7 +194,7 @@ public abstract class SimAbstractMachine(
* The [SimStorageInterface] implementation for a machine.
*/
private class StorageDeviceImpl(
- interpreter: SimResourceInterpreter,
+ engine: FlowEngine,
model: StorageDevice,
index: Int
) : SimStorageInterface {
@@ -197,9 +202,9 @@ public abstract class SimAbstractMachine(
override val capacity: Double = model.capacity
- override val read: SimResourceProvider = SimResourceSource(model.readBandwidth, interpreter)
+ override val read: FlowConsumer = FlowSink(engine, model.readBandwidth)
- override val write: SimResourceProvider = SimResourceSource(model.writeBandwidth, interpreter)
+ override val write: FlowConsumer = FlowSink(engine, model.writeBandwidth)
override fun toString(): String = "SimAbstractMachine.StorageDeviceImpl[name=$name,capacity=$capacity]"
}
diff --git a/opendc-simulator/opendc-simulator-compute/src/main/kotlin/org/opendc/simulator/compute/SimBareMetalMachine.kt b/opendc-simulator/opendc-simulator-compute/src/main/kotlin/org/opendc/simulator/compute/SimBareMetalMachine.kt
index 639ca450..9140d31b 100644
--- a/opendc-simulator/opendc-simulator-compute/src/main/kotlin/org/opendc/simulator/compute/SimBareMetalMachine.kt
+++ b/opendc-simulator/opendc-simulator-compute/src/main/kotlin/org/opendc/simulator/compute/SimBareMetalMachine.kt
@@ -26,8 +26,9 @@ 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.resources.*
-import org.opendc.simulator.resources.SimResourceInterpreter
+import org.opendc.simulator.flow.*
+import org.opendc.simulator.flow.FlowEngine
+import kotlin.math.max
/**
* A simulated bare-metal machine that is able to run a single workload.
@@ -35,30 +36,38 @@ import org.opendc.simulator.resources.SimResourceInterpreter
* 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 interpreter The [SimResourceInterpreter] to drive the simulation.
+ * @param engine The [FlowEngine] to drive the simulation.
* @param model The machine model to simulate.
* @param powerDriver The power driver to use.
* @param psu The power supply of the machine.
* @param parent The parent simulation system.
*/
public class SimBareMetalMachine(
- interpreter: SimResourceInterpreter,
+ engine: FlowEngine,
model: MachineModel,
powerDriver: PowerDriver,
public val psu: SimPsu = SimPsu(500.0, mapOf(1.0 to 1.0)),
- parent: SimResourceSystem? = null,
-) : SimAbstractMachine(interpreter, parent, model) {
+ parent: FlowConvergenceListener? = null,
+) : SimAbstractMachine(engine, parent, model) {
/**
- * The power draw of the machine onto the PSU.
+ * The current power usage of the machine (without PSU loss) in W.
*/
- public val powerDraw: Double
- get() = powerDriverLogic.computePower()
+ 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() = _energyUsage
+ private var _energyUsage = 0.0
/**
* The processing units of the machine.
*/
override val cpus: List<SimProcessingUnit> = model.cpus.map { cpu ->
- Cpu(SimResourceSource(cpu.frequency, interpreter, this@SimBareMetalMachine), cpu)
+ Cpu(FlowSink(engine, cpu.frequency, this@SimBareMetalMachine), cpu)
}
/**
@@ -66,8 +75,20 @@ public class SimBareMetalMachine(
*/
private val powerDriverLogic = powerDriver.createLogic(this, cpus)
- override fun onConverge(timestamp: Long) {
+ private var _lastConverge = Long.MAX_VALUE
+
+ override fun onConverge(now: Long, delta: 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
+ _energyUsage += _powerUsage * (duration / 1000.0)
+ _powerUsage = powerDriverLogic.computePower()
+ }
}
init {
@@ -78,9 +99,9 @@ public class SimBareMetalMachine(
* A [SimProcessingUnit] of a bare-metal machine.
*/
private class Cpu(
- private val source: SimResourceSource,
+ private val source: FlowSink,
override val model: ProcessingUnit
- ) : SimProcessingUnit, SimResourceProvider by source {
+ ) : SimProcessingUnit, FlowConsumer by source {
override var capacity: Double
get() = source.capacity
set(value) {
diff --git a/opendc-simulator/opendc-simulator-compute/src/main/kotlin/org/opendc/simulator/compute/SimMachine.kt b/opendc-simulator/opendc-simulator-compute/src/main/kotlin/org/opendc/simulator/compute/SimMachine.kt
index d8dd8205..ab0b56ae 100644
--- a/opendc-simulator/opendc-simulator-compute/src/main/kotlin/org/opendc/simulator/compute/SimMachine.kt
+++ b/opendc-simulator/opendc-simulator-compute/src/main/kotlin/org/opendc/simulator/compute/SimMachine.kt
@@ -41,7 +41,7 @@ public interface SimMachine : AutoCloseable {
public val peripherals: List<SimPeripheral>
/**
- * Run the specified [SimWorkload] on this machine and suspend execution util the workload has finished.
+ * Converge the specified [SimWorkload] on this machine and suspend execution util the workload has finished.
*/
public suspend fun run(workload: SimWorkload, meta: Map<String, Any> = emptyMap())
diff --git a/opendc-simulator/opendc-simulator-compute/src/main/kotlin/org/opendc/simulator/compute/SimMachineContext.kt b/opendc-simulator/opendc-simulator-compute/src/main/kotlin/org/opendc/simulator/compute/SimMachineContext.kt
index 6996a30d..1317f728 100644
--- a/opendc-simulator/opendc-simulator-compute/src/main/kotlin/org/opendc/simulator/compute/SimMachineContext.kt
+++ b/opendc-simulator/opendc-simulator-compute/src/main/kotlin/org/opendc/simulator/compute/SimMachineContext.kt
@@ -22,7 +22,7 @@
package org.opendc.simulator.compute
-import org.opendc.simulator.resources.SimResourceInterpreter
+import org.opendc.simulator.flow.FlowEngine
/**
* A simulated execution context in which a bootable image runs. This interface represents the
@@ -31,9 +31,9 @@ import org.opendc.simulator.resources.SimResourceInterpreter
*/
public interface SimMachineContext : AutoCloseable {
/**
- * The resource interpreter that simulates the machine.
+ * The [FlowEngine] that simulates the machine.
*/
- public val interpreter: SimResourceInterpreter
+ public val engine: FlowEngine
/**
* The metadata associated with the context.
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/kotlin/org/opendc/simulator/compute/SimMemory.kt
index 6623df23..b1aef495 100644
--- a/opendc-simulator/opendc-simulator-compute/src/main/kotlin/org/opendc/simulator/compute/SimMemory.kt
+++ b/opendc-simulator/opendc-simulator-compute/src/main/kotlin/org/opendc/simulator/compute/SimMemory.kt
@@ -23,12 +23,12 @@
package org.opendc.simulator.compute
import org.opendc.simulator.compute.model.MemoryUnit
-import org.opendc.simulator.resources.SimResourceProvider
+import org.opendc.simulator.flow.FlowConsumer
/**
* An interface to control the memory usage of simulated workloads.
*/
-public interface SimMemory : SimResourceProvider {
+public interface SimMemory : FlowConsumer {
/**
* The models representing the static information of the memory units supporting this interface.
*/
diff --git a/opendc-simulator/opendc-simulator-compute/src/main/kotlin/org/opendc/simulator/compute/SimNetworkInterface.kt b/opendc-simulator/opendc-simulator-compute/src/main/kotlin/org/opendc/simulator/compute/SimNetworkInterface.kt
index 1ac126ae..660b2871 100644
--- a/opendc-simulator/opendc-simulator-compute/src/main/kotlin/org/opendc/simulator/compute/SimNetworkInterface.kt
+++ b/opendc-simulator/opendc-simulator-compute/src/main/kotlin/org/opendc/simulator/compute/SimNetworkInterface.kt
@@ -22,8 +22,8 @@
package org.opendc.simulator.compute
-import org.opendc.simulator.resources.SimResourceConsumer
-import org.opendc.simulator.resources.SimResourceProvider
+import org.opendc.simulator.flow.FlowConsumer
+import org.opendc.simulator.flow.FlowSource
/**
* A firmware interface to a network adapter.
@@ -42,10 +42,10 @@ public interface SimNetworkInterface {
/**
* The resource provider for the transmit channel of the network interface.
*/
- public val tx: SimResourceProvider
+ public val tx: FlowConsumer
/**
* The resource consumer for the receive channel of the network interface.
*/
- public val rx: SimResourceConsumer
+ public val rx: FlowSource
}
diff --git a/opendc-simulator/opendc-simulator-compute/src/main/kotlin/org/opendc/simulator/compute/SimProcessingUnit.kt b/opendc-simulator/opendc-simulator-compute/src/main/kotlin/org/opendc/simulator/compute/SimProcessingUnit.kt
index 93c9ddfa..c9f36ece 100644
--- a/opendc-simulator/opendc-simulator-compute/src/main/kotlin/org/opendc/simulator/compute/SimProcessingUnit.kt
+++ b/opendc-simulator/opendc-simulator-compute/src/main/kotlin/org/opendc/simulator/compute/SimProcessingUnit.kt
@@ -23,12 +23,12 @@
package org.opendc.simulator.compute
import org.opendc.simulator.compute.model.ProcessingUnit
-import org.opendc.simulator.resources.SimResourceProvider
+import org.opendc.simulator.flow.FlowConsumer
/**
* A simulated processing unit.
*/
-public interface SimProcessingUnit : SimResourceProvider {
+public interface SimProcessingUnit : FlowConsumer {
/**
* The capacity of the processing unit, which can be adjusted by the workload if supported by the machine.
*/
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/kotlin/org/opendc/simulator/compute/SimStorageInterface.kt
index 21a801f1..3d648671 100644
--- a/opendc-simulator/opendc-simulator-compute/src/main/kotlin/org/opendc/simulator/compute/SimStorageInterface.kt
+++ b/opendc-simulator/opendc-simulator-compute/src/main/kotlin/org/opendc/simulator/compute/SimStorageInterface.kt
@@ -22,7 +22,7 @@
package org.opendc.simulator.compute
-import org.opendc.simulator.resources.SimResourceProvider
+import org.opendc.simulator.flow.FlowConsumer
/**
* A firmware interface to a storage device.
@@ -41,10 +41,10 @@ public interface SimStorageInterface {
/**
* The resource provider for the read operations of the storage device.
*/
- public val read: SimResourceProvider
+ public val read: FlowConsumer
/**
* The resource consumer for the write operation of the storage device.
*/
- public val write: SimResourceProvider
+ public val write: FlowConsumer
}
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
index 0a7dc40f..09defbb5 100644
--- 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
@@ -23,11 +23,9 @@
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 org.opendc.simulator.resources.SimResourceCommand
-import org.opendc.simulator.resources.SimResourceConsumer
-import org.opendc.simulator.resources.SimResourceContext
-import org.opendc.simulator.resources.SimResourceEvent
import java.util.*
/**
@@ -55,7 +53,7 @@ public class SimPsu(
/**
* The consumer context.
*/
- private var _ctx: SimResourceContext? = null
+ private var _ctx: FlowConnection? = null
/**
* The driver that is connected to the PSU.
@@ -70,7 +68,7 @@ public class SimPsu(
* Update the power draw of the PSU.
*/
public fun update() {
- _ctx?.interrupt()
+ _ctx?.pull()
}
/**
@@ -82,23 +80,24 @@ public class SimPsu(
update()
}
- override fun createConsumer(): SimResourceConsumer = object : SimResourceConsumer {
- override fun onNext(ctx: SimResourceContext): SimResourceCommand {
- val powerDraw = computePowerDraw(_driver?.computePower() ?: 0.0)
+ override fun createSource(): FlowSource = object : FlowSource {
+ override fun onStart(conn: FlowConnection, now: Long) {
+ _ctx = conn
+ conn.shouldSourceConverge = true
+ }
- return if (powerDraw > 0.0)
- SimResourceCommand.Consume(Double.POSITIVE_INFINITY, powerDraw, Long.MAX_VALUE)
- else
- SimResourceCommand.Idle()
+ override fun onStop(conn: FlowConnection, now: Long, delta: Long) {
+ _ctx = null
+ }
+
+ override fun onPull(conn: FlowConnection, now: Long, delta: Long): Long {
+ val powerDraw = computePowerDraw(_driver?.computePower() ?: 0.0)
+ conn.push(powerDraw)
+ return Long.MAX_VALUE
}
- override fun onEvent(ctx: SimResourceContext, event: SimResourceEvent) {
- when (event) {
- SimResourceEvent.Start -> _ctx = ctx
- SimResourceEvent.Run -> _powerDraw = ctx.speed
- SimResourceEvent.Exit -> _ctx = null
- else -> {}
- }
+ override fun onConverge(conn: FlowConnection, now: Long, delta: Long) {
+ _powerDraw = conn.rate
}
}
diff --git a/opendc-simulator/opendc-simulator-compute/src/main/kotlin/org/opendc/simulator/compute/kernel/SimAbstractHypervisor.kt b/opendc-simulator/opendc-simulator-compute/src/main/kotlin/org/opendc/simulator/compute/kernel/SimAbstractHypervisor.kt
index 98271fb0..aac8b959 100644
--- a/opendc-simulator/opendc-simulator-compute/src/main/kotlin/org/opendc/simulator/compute/kernel/SimAbstractHypervisor.kt
+++ b/opendc-simulator/opendc-simulator-compute/src/main/kotlin/org/opendc/simulator/compute/kernel/SimAbstractHypervisor.kt
@@ -28,29 +28,31 @@ import org.opendc.simulator.compute.kernel.cpufreq.ScalingPolicy
import org.opendc.simulator.compute.kernel.interference.VmInterferenceDomain
import org.opendc.simulator.compute.model.MachineModel
import org.opendc.simulator.compute.model.ProcessingUnit
-import org.opendc.simulator.resources.*
-import org.opendc.simulator.resources.SimResourceSwitch
+import org.opendc.simulator.flow.*
+import org.opendc.simulator.flow.mux.FlowMultiplexer
+import kotlin.math.roundToLong
/**
* Abstract implementation of the [SimHypervisor] interface.
*
- * @param interpreter The resource interpreter to use.
+ * @param engine The [FlowEngine] to drive the simulation.
* @param scalingGovernor The scaling governor to use for scaling the CPU frequency of the underlying hardware.
*/
public abstract class SimAbstractHypervisor(
- private val interpreter: SimResourceInterpreter,
- private val scalingGovernor: ScalingGovernor? = null,
+ protected val engine: FlowEngine,
+ private val listener: FlowConvergenceListener?,
+ private val scalingGovernor: ScalingGovernor?,
protected val interferenceDomain: VmInterferenceDomain? = null
-) : SimHypervisor {
+) : SimHypervisor, FlowConvergenceListener {
/**
* The machine on which the hypervisor runs.
*/
- private lateinit var context: SimMachineContext
+ protected lateinit var context: SimMachineContext
/**
* The resource switch to use.
*/
- private lateinit var switch: SimResourceSwitch
+ protected abstract val mux: FlowMultiplexer
/**
* The virtual machines running on this hypervisor.
@@ -62,39 +64,73 @@ public abstract class SimAbstractHypervisor(
/**
* The resource counters associated with the hypervisor.
*/
- public override val counters: SimResourceCounters
- get() = switch.counters
+ public override val counters: SimHypervisorCounters
+ get() = _counters
+ private val _counters = object : SimHypervisorCounters {
+ @JvmField var d = 1.0 // Number of CPUs divided by total CPU capacity
+
+ override var cpuActiveTime: Long = 0L
+ override var cpuIdleTime: Long = 0L
+ override var cpuStealTime: Long = 0L
+ override var cpuLostTime: Long = 0L
+
+ private var _previousDemand = 0.0
+ private var _previousActual = 0.0
+ private var _previousRemaining = 0.0
+ private var _previousInterference = 0.0
+
+ /**
+ * Record the CPU time of the hypervisor.
+ */
+ fun record() {
+ val counters = mux.counters
+ val demand = counters.demand
+ val actual = counters.actual
+ val remaining = counters.remaining
+ val interference = counters.interference
+
+ val demandDelta = demand - _previousDemand
+ val actualDelta = actual - _previousActual
+ val remainingDelta = remaining - _previousRemaining
+ val interferenceDelta = interference - _previousInterference
+
+ _previousDemand = demand
+ _previousActual = actual
+ _previousRemaining = remaining
+ _previousInterference = interference
+
+ cpuActiveTime += (actualDelta * d).roundToLong()
+ cpuIdleTime += (remainingDelta * d).roundToLong()
+ cpuStealTime += ((demandDelta - actualDelta) * d).roundToLong()
+ cpuLostTime += (interferenceDelta * d).roundToLong()
+ }
+ }
/**
- * The scaling governors attached to the physical CPUs backing this hypervisor.
+ * The CPU capacity of the hypervisor in MHz.
*/
- private val governors = mutableListOf<ScalingGovernor.Logic>()
+ override val cpuCapacity: Double
+ get() = mux.capacity
/**
- * Construct the [SimResourceSwitch] implementation that performs the actual scheduling of the CPUs.
+ * The CPU demand of the hypervisor in MHz.
*/
- public abstract fun createSwitch(ctx: SimMachineContext): SimResourceSwitch
+ override val cpuDemand: Double
+ get() = mux.demand
/**
- * Check whether the specified machine model fits on this hypervisor.
+ * The CPU usage of the hypervisor in MHz.
*/
- public abstract fun canFit(model: MachineModel, switch: SimResourceSwitch): Boolean
+ override val cpuUsage: Double
+ get() = mux.rate
/**
- * Trigger the governors to recompute the scaling limits.
+ * The scaling governors attached to the physical CPUs backing this hypervisor.
*/
- protected fun triggerGovernors(load: Double) {
- for (governor in governors) {
- governor.onLimit(load)
- }
- }
+ private val governors = mutableListOf<ScalingGovernor.Logic>()
/* SimHypervisor */
- override fun canFit(model: MachineModel): Boolean {
- return canFit(model, switch)
- }
-
- override fun createMachine(model: MachineModel, interferenceId: String?): SimMachine {
+ override fun createMachine(model: MachineModel, interferenceId: String?): SimVirtualMachine {
require(canFit(model)) { "Machine does not fit" }
val vm = VirtualMachine(model, interferenceId)
_vms.add(vm)
@@ -104,7 +140,13 @@ public abstract class SimAbstractHypervisor(
/* SimWorkload */
override fun onStart(ctx: SimMachineContext) {
context = ctx
- switch = createSwitch(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))
@@ -113,16 +155,31 @@ public abstract class SimAbstractHypervisor(
governor.onStart()
}
- switch.addInput(cpu)
+ cpu.startConsumer(mux.newOutput())
}
}
+ private var _cpuCount = 0
+ private var _cpuCapacity = 0.0
+
+ /* FlowConvergenceListener */
+ override fun onConverge(now: Long, delta: Long) {
+ _counters.record()
+
+ val load = cpuDemand / cpuCapacity
+ for (governor in governors) {
+ governor.onLimit(load)
+ }
+
+ listener?.onConverge(now, delta)
+ }
+
/**
* A virtual machine running on the hypervisor.
*
* @param model The machine model of the virtual machine.
*/
- private inner class VirtualMachine(model: MachineModel, interferenceId: String? = null) : SimAbstractMachine(interpreter, parent = null, model) {
+ private inner class VirtualMachine(model: MachineModel, interferenceId: String? = null) : SimAbstractMachine(engine, parent = null, model), SimVirtualMachine {
/**
* The interference key of this virtual machine.
*/
@@ -131,7 +188,42 @@ public abstract class SimAbstractHypervisor(
/**
* The vCPUs of the machine.
*/
- override val cpus = model.cpus.map { VCpu(switch.newOutput(interferenceKey), it) }
+ override val cpus = model.cpus.map { VCpu(mux, mux.newInput(interferenceKey), it) }
+
+ /**
+ * The resource counters associated with the hypervisor.
+ */
+ override val counters: SimHypervisorCounters
+ get() = _counters
+ private val _counters = object : SimHypervisorCounters {
+ private val d = cpus.size / cpus.sumOf { it.model.frequency } * 1000
+
+ override val cpuActiveTime: Long
+ get() = (cpus.sumOf { it.counters.actual } * d).roundToLong()
+ override val cpuIdleTime: Long
+ get() = (cpus.sumOf { it.counters.actual + it.counters.remaining } * d).roundToLong()
+ override val cpuStealTime: Long
+ get() = (cpus.sumOf { it.counters.demand - it.counters.actual } * d).roundToLong()
+ override val cpuLostTime: Long = 0L
+ }
+
+ /**
+ * The CPU capacity of the hypervisor in MHz.
+ */
+ override val cpuCapacity: Double
+ get() = cpus.sumOf(FlowConsumer::capacity)
+
+ /**
+ * The CPU demand of the hypervisor in MHz.
+ */
+ override val cpuDemand: Double
+ get() = cpus.sumOf(FlowConsumer::demand)
+
+ /**
+ * The CPU usage of the hypervisor in MHz.
+ */
+ override val cpuUsage: Double
+ get() = cpus.sumOf(FlowConsumer::rate)
override fun close() {
super.close()
@@ -145,17 +237,16 @@ public abstract class SimAbstractHypervisor(
interferenceDomain?.leave(interferenceKey)
}
}
-
- override fun onConverge(timestamp: Long) {}
}
/**
* A [SimProcessingUnit] of a virtual machine.
*/
private class VCpu(
- private val source: SimResourceCloseableProvider,
+ private val switch: FlowMultiplexer,
+ private val source: FlowConsumer,
override val model: ProcessingUnit
- ) : SimProcessingUnit, SimResourceCloseableProvider by source {
+ ) : SimProcessingUnit, FlowConsumer by source {
override var capacity: Double
get() = source.capacity
set(_) {
@@ -163,6 +254,13 @@ public abstract class SimAbstractHypervisor(
}
override fun toString(): String = "SimAbstractHypervisor.VCpu[model=$model]"
+
+ /**
+ * Close the CPU
+ */
+ fun close() {
+ switch.removeInput(source)
+ }
}
/**
diff --git a/opendc-simulator/opendc-simulator-compute/src/main/kotlin/org/opendc/simulator/compute/kernel/SimFairShareHypervisor.kt b/opendc-simulator/opendc-simulator-compute/src/main/kotlin/org/opendc/simulator/compute/kernel/SimFairShareHypervisor.kt
index 3b44292d..36f76650 100644
--- a/opendc-simulator/opendc-simulator-compute/src/main/kotlin/org/opendc/simulator/compute/kernel/SimFairShareHypervisor.kt
+++ b/opendc-simulator/opendc-simulator-compute/src/main/kotlin/org/opendc/simulator/compute/kernel/SimFairShareHypervisor.kt
@@ -23,79 +23,34 @@
package org.opendc.simulator.compute.kernel
import org.opendc.simulator.compute.SimMachine
-import org.opendc.simulator.compute.SimMachineContext
import org.opendc.simulator.compute.kernel.cpufreq.ScalingGovernor
import org.opendc.simulator.compute.kernel.interference.VmInterferenceDomain
import org.opendc.simulator.compute.model.MachineModel
import org.opendc.simulator.compute.workload.SimWorkload
-import org.opendc.simulator.resources.SimResourceInterpreter
-import org.opendc.simulator.resources.SimResourceSwitch
-import org.opendc.simulator.resources.SimResourceSwitchMaxMin
-import org.opendc.simulator.resources.SimResourceSystem
+import org.opendc.simulator.flow.FlowConvergenceListener
+import org.opendc.simulator.flow.FlowEngine
+import org.opendc.simulator.flow.mux.FlowMultiplexer
+import org.opendc.simulator.flow.mux.MaxMinFlowMultiplexer
/**
* A [SimHypervisor] that distributes the computing requirements of multiple [SimWorkload]s on a single [SimMachine]
* concurrently using weighted fair sharing.
*
- * @param interpreter The interpreter to manage the machine's resources.
- * @param parent The parent simulation system.
+ * @param engine The [FlowEngine] to manage the machine's resources.
+ * @param listener The listener for the convergence of the system.
* @param scalingGovernor The CPU frequency scaling governor to use for the hypervisor.
* @param interferenceDomain The resource interference domain to which the hypervisor belongs.
- * @param listener The hypervisor listener to use.
*/
public class SimFairShareHypervisor(
- private val interpreter: SimResourceInterpreter,
- private val parent: SimResourceSystem? = null,
- scalingGovernor: ScalingGovernor? = null,
- interferenceDomain: VmInterferenceDomain? = null,
- private val listener: SimHypervisor.Listener? = null
-) : SimAbstractHypervisor(interpreter, scalingGovernor, interferenceDomain) {
-
- override fun canFit(model: MachineModel, switch: SimResourceSwitch): Boolean = true
-
- override fun createSwitch(ctx: SimMachineContext): SimResourceSwitch {
- return SwitchSystem(ctx).switch
- }
-
- private inner class SwitchSystem(private val ctx: SimMachineContext) : SimResourceSystem {
- val switch = SimResourceSwitchMaxMin(interpreter, this, interferenceDomain)
-
- override val parent: SimResourceSystem? = this@SimFairShareHypervisor.parent
-
- private var lastCpuUsage = 0.0
- private var lastCpuDemand = 0.0
- private var lastDemand = 0.0
- private var lastActual = 0.0
- private var lastOvercommit = 0.0
- private var lastInterference = 0.0
- private var lastReport = Long.MIN_VALUE
-
- override fun onConverge(timestamp: Long) {
- val listener = listener ?: return
- val counters = switch.counters
-
- if (timestamp > lastReport) {
- listener.onSliceFinish(
- this@SimFairShareHypervisor,
- counters.demand - lastDemand,
- counters.actual - lastActual,
- counters.overcommit - lastOvercommit,
- counters.interference - lastInterference,
- lastCpuUsage,
- lastCpuDemand
- )
- }
- lastReport = timestamp
-
- lastCpuDemand = switch.inputs.sumOf { it.demand }
- lastCpuUsage = switch.inputs.sumOf { it.speed }
- lastDemand = counters.demand
- lastActual = counters.actual
- lastOvercommit = counters.overcommit
- lastInterference = counters.interference
-
- val load = lastCpuDemand / ctx.cpus.sumOf { it.model.frequency }
- triggerGovernors(load)
- }
- }
+ engine: FlowEngine,
+ listener: FlowConvergenceListener?,
+ scalingGovernor: ScalingGovernor?,
+ interferenceDomain: VmInterferenceDomain?,
+) : SimAbstractHypervisor(engine, listener, scalingGovernor, interferenceDomain) {
+ /**
+ * The multiplexer that distributes the computing capacity.
+ */
+ override val mux: FlowMultiplexer = MaxMinFlowMultiplexer(engine, this, interferenceDomain)
+
+ override fun canFit(model: MachineModel): Boolean = true
}
diff --git a/opendc-simulator/opendc-simulator-compute/src/main/kotlin/org/opendc/simulator/compute/kernel/SimFairShareHypervisorProvider.kt b/opendc-simulator/opendc-simulator-compute/src/main/kotlin/org/opendc/simulator/compute/kernel/SimFairShareHypervisorProvider.kt
index 8d0592ec..3136f4c8 100644
--- a/opendc-simulator/opendc-simulator-compute/src/main/kotlin/org/opendc/simulator/compute/kernel/SimFairShareHypervisorProvider.kt
+++ b/opendc-simulator/opendc-simulator-compute/src/main/kotlin/org/opendc/simulator/compute/kernel/SimFairShareHypervisorProvider.kt
@@ -24,8 +24,8 @@ package org.opendc.simulator.compute.kernel
import org.opendc.simulator.compute.kernel.cpufreq.ScalingGovernor
import org.opendc.simulator.compute.kernel.interference.VmInterferenceDomain
-import org.opendc.simulator.resources.SimResourceInterpreter
-import org.opendc.simulator.resources.SimResourceSystem
+import org.opendc.simulator.flow.FlowConvergenceListener
+import org.opendc.simulator.flow.FlowEngine
/**
* A [SimHypervisorProvider] for the [SimFairShareHypervisor] implementation.
@@ -34,16 +34,9 @@ public class SimFairShareHypervisorProvider : SimHypervisorProvider {
override val id: String = "fair-share"
override fun create(
- interpreter: SimResourceInterpreter,
- parent: SimResourceSystem?,
+ engine: FlowEngine,
+ listener: FlowConvergenceListener?,
scalingGovernor: ScalingGovernor?,
interferenceDomain: VmInterferenceDomain?,
- listener: SimHypervisor.Listener?
- ): SimHypervisor = SimFairShareHypervisor(
- interpreter,
- parent,
- scalingGovernor = scalingGovernor,
- interferenceDomain = interferenceDomain,
- listener = listener
- )
+ ): SimHypervisor = SimFairShareHypervisor(engine, listener, scalingGovernor, interferenceDomain)
}
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
index 3b49d515..57d4cf20 100644
--- 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
@@ -25,7 +25,6 @@ package org.opendc.simulator.compute.kernel
import org.opendc.simulator.compute.SimMachine
import org.opendc.simulator.compute.model.MachineModel
import org.opendc.simulator.compute.workload.SimWorkload
-import org.opendc.simulator.resources.SimResourceCounters
/**
* A SimHypervisor facilitates the execution of multiple concurrent [SimWorkload]s, while acting as a single workload
@@ -40,7 +39,22 @@ public interface SimHypervisor : SimWorkload {
/**
* The resource counters associated with the hypervisor.
*/
- public val counters: SimResourceCounters
+ public val counters: SimHypervisorCounters
+
+ /**
+ * The CPU usage of the hypervisor in MHz.
+ */
+ public val cpuUsage: Double
+
+ /**
+ * The CPU usage of the hypervisor in MHz.
+ */
+ public val cpuDemand: Double
+
+ /**
+ * The CPU capacity of the hypervisor in MHz.
+ */
+ public val cpuCapacity: Double
/**
* Determine whether the specified machine characterized by [model] can fit on this hypervisor at this moment.
@@ -53,23 +67,5 @@ public interface SimHypervisor : SimWorkload {
* @param model The machine to create.
* @param interferenceId An identifier for the interference model.
*/
- public fun createMachine(model: MachineModel, interferenceId: String? = null): SimMachine
-
- /**
- * Event listener for hypervisor events.
- */
- public interface Listener {
- /**
- * This method is invoked when a slice is finished.
- */
- public fun onSliceFinish(
- hypervisor: SimHypervisor,
- totalWork: Double,
- grantedWork: Double,
- overcommittedWork: Double,
- interferedWork: Double,
- cpuUsage: Double,
- cpuDemand: Double
- )
- }
+ public fun createMachine(model: MachineModel, interferenceId: String? = null): SimVirtualMachine
}
diff --git a/opendc-simulator/opendc-simulator-resources/src/main/kotlin/org/opendc/simulator/resources/SimResourceDistributor.kt b/opendc-simulator/opendc-simulator-compute/src/main/kotlin/org/opendc/simulator/compute/kernel/SimHypervisorCounters.kt
index f384582f..030d9c5f 100644
--- a/opendc-simulator/opendc-simulator-resources/src/main/kotlin/org/opendc/simulator/resources/SimResourceDistributor.kt
+++ b/opendc-simulator/opendc-simulator-compute/src/main/kotlin/org/opendc/simulator/compute/kernel/SimHypervisorCounters.kt
@@ -20,23 +20,29 @@
* SOFTWARE.
*/
-package org.opendc.simulator.resources
-
-import org.opendc.simulator.resources.interference.InterferenceKey
+package org.opendc.simulator.compute.kernel
/**
- * A [SimResourceDistributor] distributes the capacity of some resource over multiple resource consumers.
+ * Performance counters of a [SimHypervisor].
*/
-public interface SimResourceDistributor : SimResourceConsumer {
+public interface SimHypervisorCounters {
+ /**
+ * The amount of time (in milliseconds) the CPUs of the hypervisor were actively running.
+ */
+ public val cpuActiveTime: Long
+
+ /**
+ * The amount of time (in milliseconds) the CPUs of the hypervisor were idle.
+ */
+ public val cpuIdleTime: Long
+
/**
- * The output resource providers to which resource consumers can be attached.
+ * The amount of CPU time (in milliseconds) that virtual machines were ready to run, but were not able to.
*/
- public val outputs: Set<SimResourceCloseableProvider>
+ public val cpuStealTime: Long
/**
- * Create a new output for the distributor.
- *
- * @param key The key of the interference member to which the output belongs.
+ * The amount of CPU time (in milliseconds) that was lost due to interference between virtual machines.
*/
- public fun newOutput(key: InterferenceKey? = null): SimResourceCloseableProvider
+ public val cpuLostTime: Long
}
diff --git a/opendc-simulator/opendc-simulator-compute/src/main/kotlin/org/opendc/simulator/compute/kernel/SimHypervisorProvider.kt b/opendc-simulator/opendc-simulator-compute/src/main/kotlin/org/opendc/simulator/compute/kernel/SimHypervisorProvider.kt
index b307a34d..483217af 100644
--- a/opendc-simulator/opendc-simulator-compute/src/main/kotlin/org/opendc/simulator/compute/kernel/SimHypervisorProvider.kt
+++ b/opendc-simulator/opendc-simulator-compute/src/main/kotlin/org/opendc/simulator/compute/kernel/SimHypervisorProvider.kt
@@ -24,8 +24,8 @@ package org.opendc.simulator.compute.kernel
import org.opendc.simulator.compute.kernel.cpufreq.ScalingGovernor
import org.opendc.simulator.compute.kernel.interference.VmInterferenceDomain
-import org.opendc.simulator.resources.SimResourceInterpreter
-import org.opendc.simulator.resources.SimResourceSystem
+import org.opendc.simulator.flow.FlowConvergenceListener
+import org.opendc.simulator.flow.FlowEngine
/**
* A service provider interface for constructing a [SimHypervisor].
@@ -40,13 +40,12 @@ public interface SimHypervisorProvider {
public val id: String
/**
- * Create a [SimHypervisor] instance with the specified [listener].
+ * Create a new [SimHypervisor] instance.
*/
public fun create(
- interpreter: SimResourceInterpreter,
- parent: SimResourceSystem? = null,
+ engine: FlowEngine,
+ listener: FlowConvergenceListener? = null,
scalingGovernor: ScalingGovernor? = null,
interferenceDomain: VmInterferenceDomain? = null,
- listener: SimHypervisor.Listener? = null
): SimHypervisor
}
diff --git a/opendc-simulator/opendc-simulator-compute/src/main/kotlin/org/opendc/simulator/compute/kernel/SimSpaceSharedHypervisor.kt b/opendc-simulator/opendc-simulator-compute/src/main/kotlin/org/opendc/simulator/compute/kernel/SimSpaceSharedHypervisor.kt
index ac1c0250..82f8df38 100644
--- a/opendc-simulator/opendc-simulator-compute/src/main/kotlin/org/opendc/simulator/compute/kernel/SimSpaceSharedHypervisor.kt
+++ b/opendc-simulator/opendc-simulator-compute/src/main/kotlin/org/opendc/simulator/compute/kernel/SimSpaceSharedHypervisor.kt
@@ -22,21 +22,24 @@
package org.opendc.simulator.compute.kernel
-import org.opendc.simulator.compute.SimMachineContext
+import org.opendc.simulator.compute.kernel.cpufreq.ScalingGovernor
import org.opendc.simulator.compute.model.MachineModel
-import org.opendc.simulator.resources.SimResourceInterpreter
-import org.opendc.simulator.resources.SimResourceSwitch
-import org.opendc.simulator.resources.SimResourceSwitchExclusive
+import org.opendc.simulator.flow.FlowConvergenceListener
+import org.opendc.simulator.flow.FlowEngine
+import org.opendc.simulator.flow.mux.FlowMultiplexer
+import org.opendc.simulator.flow.mux.ForwardingFlowMultiplexer
/**
* A [SimHypervisor] that allocates its sub-resources exclusively for the virtual machine that it hosts.
*/
-public class SimSpaceSharedHypervisor(interpreter: SimResourceInterpreter) : SimAbstractHypervisor(interpreter) {
- override fun canFit(model: MachineModel, switch: SimResourceSwitch): Boolean {
- return switch.inputs.size - switch.outputs.size >= model.cpus.size
- }
+public class SimSpaceSharedHypervisor(
+ engine: FlowEngine,
+ listener: FlowConvergenceListener?,
+ scalingGovernor: ScalingGovernor?,
+) : SimAbstractHypervisor(engine, listener, scalingGovernor) {
+ override val mux: FlowMultiplexer = ForwardingFlowMultiplexer(engine)
- override fun createSwitch(ctx: SimMachineContext): SimResourceSwitch {
- return SimResourceSwitchExclusive()
+ override fun canFit(model: MachineModel): Boolean {
+ return mux.outputs.size - mux.inputs.size >= model.cpus.size
}
}
diff --git a/opendc-simulator/opendc-simulator-compute/src/main/kotlin/org/opendc/simulator/compute/kernel/SimSpaceSharedHypervisorProvider.kt b/opendc-simulator/opendc-simulator-compute/src/main/kotlin/org/opendc/simulator/compute/kernel/SimSpaceSharedHypervisorProvider.kt
index 3906cb9a..dd6fb0b1 100644
--- a/opendc-simulator/opendc-simulator-compute/src/main/kotlin/org/opendc/simulator/compute/kernel/SimSpaceSharedHypervisorProvider.kt
+++ b/opendc-simulator/opendc-simulator-compute/src/main/kotlin/org/opendc/simulator/compute/kernel/SimSpaceSharedHypervisorProvider.kt
@@ -24,8 +24,8 @@ package org.opendc.simulator.compute.kernel
import org.opendc.simulator.compute.kernel.cpufreq.ScalingGovernor
import org.opendc.simulator.compute.kernel.interference.VmInterferenceDomain
-import org.opendc.simulator.resources.SimResourceInterpreter
-import org.opendc.simulator.resources.SimResourceSystem
+import org.opendc.simulator.flow.FlowConvergenceListener
+import org.opendc.simulator.flow.FlowEngine
/**
* A [SimHypervisorProvider] for the [SimSpaceSharedHypervisor] implementation.
@@ -34,10 +34,9 @@ public class SimSpaceSharedHypervisorProvider : SimHypervisorProvider {
override val id: String = "space-shared"
override fun create(
- interpreter: SimResourceInterpreter,
- parent: SimResourceSystem?,
+ engine: FlowEngine,
+ listener: FlowConvergenceListener?,
scalingGovernor: ScalingGovernor?,
interferenceDomain: VmInterferenceDomain?,
- listener: SimHypervisor.Listener?
- ): SimHypervisor = SimSpaceSharedHypervisor(interpreter)
+ ): SimHypervisor = SimSpaceSharedHypervisor(engine, listener, scalingGovernor)
}
diff --git a/opendc-simulator/opendc-simulator-resources/src/main/kotlin/org/opendc/simulator/resources/SimResourceEvent.kt b/opendc-simulator/opendc-simulator-compute/src/main/kotlin/org/opendc/simulator/compute/kernel/SimVirtualMachine.kt
index 959427f1..36219ef2 100644
--- a/opendc-simulator/opendc-simulator-resources/src/main/kotlin/org/opendc/simulator/resources/SimResourceEvent.kt
+++ b/opendc-simulator/opendc-simulator-compute/src/main/kotlin/org/opendc/simulator/compute/kernel/SimVirtualMachine.kt
@@ -20,29 +20,31 @@
* SOFTWARE.
*/
-package org.opendc.simulator.resources
+package org.opendc.simulator.compute.kernel
+
+import org.opendc.simulator.compute.SimMachine
/**
- * A resource event that is communicated to the resource consumer.
+ * A virtual [SimMachine] running on top of another [SimMachine].
*/
-public enum class SimResourceEvent {
+public interface SimVirtualMachine : SimMachine {
/**
- * This event is emitted to the consumer when it has started.
+ * The resource counters associated with the virtual machine.
*/
- Start,
+ public val counters: SimHypervisorCounters
/**
- * This event is emitted to the consumer when it has exited.
+ * The CPU usage of the VM in MHz.
*/
- Exit,
+ public val cpuUsage: Double
/**
- * This event is emitted to the consumer when it has started a new resource consumption or idle cycle.
+ * The CPU usage of the VM in MHz.
*/
- Run,
+ public val cpuDemand: Double
/**
- * This event is emitted to the consumer when the capacity of the resource has changed.
+ * The CPU capacity of the VM in MHz.
*/
- Capacity,
+ public val cpuCapacity: Double
}
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
index 1801fcd0..b737d61a 100644
--- 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
@@ -22,8 +22,8 @@
package org.opendc.simulator.compute.kernel.interference
-import org.opendc.simulator.resources.interference.InterferenceDomain
-import org.opendc.simulator.resources.interference.InterferenceKey
+import org.opendc.simulator.flow.interference.InterferenceDomain
+import org.opendc.simulator.flow.interference.InterferenceKey
/**
* The interference domain of a hypervisor.
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
index c2e00c8e..b3d72507 100644
--- 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
@@ -22,7 +22,7 @@
package org.opendc.simulator.compute.kernel.interference
-import org.opendc.simulator.resources.interference.InterferenceKey
+import org.opendc.simulator.flow.interference.InterferenceKey
import java.util.*
/**
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
index 6577fbfc..f71446f8 100644
--- 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
@@ -46,7 +46,7 @@ public class PStatePowerDriver(states: Map<Double, PowerModel>) : PowerDriver {
for (cpu in cpus) {
targetFreq = max(cpu.capacity, targetFreq)
- totalSpeed += cpu.speed
+ totalSpeed += cpu.rate
}
val maxFreq = states.lastKey()
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
index bf7aeff1..34e91c35 100644
--- 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
@@ -37,7 +37,7 @@ public class SimplePowerDriver(private val model: PowerModel) : PowerDriver {
for (cpu in cpus) {
targetFreq += cpu.capacity
- totalSpeed += cpu.speed
+ totalSpeed += cpu.rate
}
return model.computePower(totalSpeed / targetFreq)
diff --git a/opendc-simulator/opendc-simulator-compute/src/main/kotlin/org/opendc/simulator/compute/workload/SimFlopsWorkload.kt b/opendc-simulator/opendc-simulator-compute/src/main/kotlin/org/opendc/simulator/compute/workload/SimFlopsWorkload.kt
index a01fa20c..99f4a1e1 100644
--- a/opendc-simulator/opendc-simulator-compute/src/main/kotlin/org/opendc/simulator/compute/workload/SimFlopsWorkload.kt
+++ b/opendc-simulator/opendc-simulator-compute/src/main/kotlin/org/opendc/simulator/compute/workload/SimFlopsWorkload.kt
@@ -23,7 +23,7 @@
package org.opendc.simulator.compute.workload
import org.opendc.simulator.compute.SimMachineContext
-import org.opendc.simulator.resources.consumer.SimWorkConsumer
+import org.opendc.simulator.flow.source.FixedFlowSource
/**
* A [SimWorkload] that models applications as a static number of floating point operations ([flops]) executed on
@@ -44,7 +44,7 @@ public class SimFlopsWorkload(
override fun onStart(ctx: SimMachineContext) {
val lifecycle = SimWorkloadLifecycle(ctx)
for (cpu in ctx.cpus) {
- cpu.startConsumer(lifecycle.waitFor(SimWorkConsumer(flops.toDouble() / ctx.cpus.size, utilization)))
+ cpu.startConsumer(lifecycle.waitFor(FixedFlowSource(flops.toDouble() / ctx.cpus.size, utilization)))
}
}
diff --git a/opendc-simulator/opendc-simulator-compute/src/main/kotlin/org/opendc/simulator/compute/workload/SimRuntimeWorkload.kt b/opendc-simulator/opendc-simulator-compute/src/main/kotlin/org/opendc/simulator/compute/workload/SimRuntimeWorkload.kt
index 4ee56689..2ef3bc43 100644
--- a/opendc-simulator/opendc-simulator-compute/src/main/kotlin/org/opendc/simulator/compute/workload/SimRuntimeWorkload.kt
+++ b/opendc-simulator/opendc-simulator-compute/src/main/kotlin/org/opendc/simulator/compute/workload/SimRuntimeWorkload.kt
@@ -23,7 +23,7 @@
package org.opendc.simulator.compute.workload
import org.opendc.simulator.compute.SimMachineContext
-import org.opendc.simulator.resources.consumer.SimWorkConsumer
+import org.opendc.simulator.flow.source.FixedFlowSource
/**
* A [SimWorkload] that models application execution as a single duration.
@@ -44,7 +44,7 @@ public class SimRuntimeWorkload(
val lifecycle = SimWorkloadLifecycle(ctx)
for (cpu in ctx.cpus) {
val limit = cpu.capacity * utilization
- cpu.startConsumer(lifecycle.waitFor(SimWorkConsumer((limit / 1000) * duration, utilization)))
+ cpu.startConsumer(lifecycle.waitFor(FixedFlowSource((limit / 1000) * duration, utilization)))
}
}
diff --git a/opendc-simulator/opendc-simulator-compute/src/main/kotlin/org/opendc/simulator/compute/workload/SimTraceWorkload.kt b/opendc-simulator/opendc-simulator-compute/src/main/kotlin/org/opendc/simulator/compute/workload/SimTraceWorkload.kt
index 5a4c4f44..49ae5933 100644
--- a/opendc-simulator/opendc-simulator-compute/src/main/kotlin/org/opendc/simulator/compute/workload/SimTraceWorkload.kt
+++ b/opendc-simulator/opendc-simulator-compute/src/main/kotlin/org/opendc/simulator/compute/workload/SimTraceWorkload.kt
@@ -24,9 +24,8 @@ package org.opendc.simulator.compute.workload
import org.opendc.simulator.compute.SimMachineContext
import org.opendc.simulator.compute.model.ProcessingUnit
-import org.opendc.simulator.resources.SimResourceCommand
-import org.opendc.simulator.resources.SimResourceConsumer
-import org.opendc.simulator.resources.SimResourceContext
+import org.opendc.simulator.flow.FlowConnection
+import org.opendc.simulator.flow.FlowSource
import kotlin.math.min
/**
@@ -54,14 +53,15 @@ public class SimTraceWorkload(public val trace: Sequence<Fragment>, private val
* Obtain the fragment with a timestamp equal or greater than [now].
*/
private fun pullFragment(now: Long): Fragment? {
+ // Return the most recent fragment if its starting time + duration is later than `now`
var fragment = fragment
- if (fragment != null && !fragment.isExpired(now)) {
+ if (fragment != null && fragment.timestamp + offset + fragment.duration > now) {
return fragment
}
while (iterator.hasNext()) {
fragment = iterator.next()
- if (!fragment.isExpired(now)) {
+ if (fragment.timestamp + offset + fragment.duration > now) {
this.fragment = fragment
return fragment
}
@@ -71,38 +71,38 @@ public class SimTraceWorkload(public val trace: Sequence<Fragment>, private val
return null
}
- /**
- * Determine if the specified [Fragment] is expired, i.e., it has already passed.
- */
- private fun Fragment.isExpired(now: Long): Boolean {
- val timestamp = this.timestamp + offset
- return now >= timestamp + duration
- }
+ private inner class Consumer(cpu: ProcessingUnit) : FlowSource {
+ private val offset = this@SimTraceWorkload.offset
+ private val id = cpu.id
+ private val coreCount = cpu.node.coreCount
+
+ override fun onPull(conn: FlowConnection, now: Long, delta: Long): Long {
+ val fragment = pullFragment(now)
+
+ if (fragment == null) {
+ conn.close()
+ return Long.MAX_VALUE
+ }
- private inner class Consumer(val cpu: ProcessingUnit) : SimResourceConsumer {
- override fun onNext(ctx: SimResourceContext): SimResourceCommand {
- val now = ctx.clock.millis()
- val fragment = pullFragment(now) ?: return SimResourceCommand.Exit
val timestamp = fragment.timestamp + offset
// Fragment is in the future
if (timestamp > now) {
- return SimResourceCommand.Idle(timestamp)
+ conn.push(0.0)
+ return timestamp - now
}
- val cores = min(cpu.node.coreCount, fragment.cores)
+ val cores = min(coreCount, fragment.cores)
val usage = if (fragment.cores > 0)
fragment.usage / cores
else
0.0
val deadline = timestamp + fragment.duration
val duration = deadline - now
- val work = duration * usage / 1000
- return if (cpu.id < cores && work > 0.0)
- SimResourceCommand.Consume(work, usage, deadline)
- else
- SimResourceCommand.Idle(deadline)
+ conn.push(if (id < cores && usage > 0.0) usage else 0.0)
+
+ return duration
}
}
@@ -114,5 +114,10 @@ public class SimTraceWorkload(public val trace: Sequence<Fragment>, private val
* @param usage The CPU usage during the fragment.
* @param cores The amount of cores utilized during the fragment.
*/
- public data class Fragment(val timestamp: Long, val duration: Long, val usage: Double, val cores: Int)
+ public data class Fragment(
+ @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/SimWorkloadLifecycle.kt b/opendc-simulator/opendc-simulator-compute/src/main/kotlin/org/opendc/simulator/compute/workload/SimWorkloadLifecycle.kt
index 5dd18271..cc4f1f6a 100644
--- 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
@@ -23,9 +23,8 @@
package org.opendc.simulator.compute.workload
import org.opendc.simulator.compute.SimMachineContext
-import org.opendc.simulator.resources.SimResourceConsumer
-import org.opendc.simulator.resources.SimResourceContext
-import org.opendc.simulator.resources.SimResourceEvent
+import org.opendc.simulator.flow.FlowConnection
+import org.opendc.simulator.flow.FlowSource
/**
* A helper class to manage the lifecycle of a [SimWorkload]
@@ -34,40 +33,29 @@ public class SimWorkloadLifecycle(private val ctx: SimMachineContext) {
/**
* The resource consumers which represent the lifecycle of the workload.
*/
- private val waiting = mutableSetOf<SimResourceConsumer>()
+ private val waiting = mutableSetOf<FlowSource>()
/**
* Wait for the specified [consumer] to complete before ending the lifecycle of the workload.
*/
- public fun waitFor(consumer: SimResourceConsumer): SimResourceConsumer {
+ public fun waitFor(consumer: FlowSource): FlowSource {
waiting.add(consumer)
- return object : SimResourceConsumer by consumer {
- override fun onEvent(ctx: SimResourceContext, event: SimResourceEvent) {
+ return object : FlowSource by consumer {
+ override fun onStop(conn: FlowConnection, now: Long, delta: Long) {
try {
- consumer.onEvent(ctx, event)
- } finally {
- if (event == SimResourceEvent.Exit) {
- complete(consumer)
- }
- }
- }
-
- override fun onFailure(ctx: SimResourceContext, cause: Throwable) {
- try {
- consumer.onFailure(ctx, cause)
+ consumer.onStop(conn, now, delta)
} finally {
complete(consumer)
}
}
-
override fun toString(): String = "SimWorkloadLifecycle.Consumer[delegate=$consumer]"
}
}
/**
- * Complete the specified [SimResourceConsumer].
+ * Complete the specified [FlowSource].
*/
- private fun complete(consumer: SimResourceConsumer) {
+ private fun complete(consumer: FlowSource) {
if (waiting.remove(consumer) && waiting.isEmpty()) {
ctx.close()
}
diff --git a/opendc-simulator/opendc-simulator-compute/src/test/kotlin/org/opendc/simulator/compute/SimMachineTest.kt b/opendc-simulator/opendc-simulator-compute/src/test/kotlin/org/opendc/simulator/compute/SimMachineTest.kt
index 81268879..0bb24ed8 100644
--- a/opendc-simulator/opendc-simulator-compute/src/test/kotlin/org/opendc/simulator/compute/SimMachineTest.kt
+++ b/opendc-simulator/opendc-simulator-compute/src/test/kotlin/org/opendc/simulator/compute/SimMachineTest.kt
@@ -34,10 +34,10 @@ import org.opendc.simulator.compute.workload.SimFlopsWorkload
import org.opendc.simulator.compute.workload.SimWorkload
import org.opendc.simulator.compute.workload.SimWorkloadLifecycle
import org.opendc.simulator.core.runBlockingSimulation
+import org.opendc.simulator.flow.FlowEngine
+import org.opendc.simulator.flow.source.FixedFlowSource
import org.opendc.simulator.network.SimNetworkSink
import org.opendc.simulator.power.SimPowerSource
-import org.opendc.simulator.resources.SimResourceInterpreter
-import org.opendc.simulator.resources.consumer.SimWorkConsumer
/**
* Test suite for the [SimBareMetalMachine] class.
@@ -60,7 +60,7 @@ class SimMachineTest {
@Test
fun testFlopsWorkload() = runBlockingSimulation {
val machine = SimBareMetalMachine(
- SimResourceInterpreter(coroutineContext, clock),
+ FlowEngine(coroutineContext, clock),
machineModel,
SimplePowerDriver(ConstantPowerModel(0.0))
)
@@ -83,7 +83,7 @@ class SimMachineTest {
memory = List(4) { MemoryUnit("Crucial", "MTA18ASF4G72AZ-3G2B1", 3200.0, 32_000) }
)
val machine = SimBareMetalMachine(
- SimResourceInterpreter(coroutineContext, clock),
+ FlowEngine(coroutineContext, clock),
machineModel,
SimplePowerDriver(ConstantPowerModel(0.0))
)
@@ -100,13 +100,13 @@ class SimMachineTest {
@Test
fun testPower() = runBlockingSimulation {
- val interpreter = SimResourceInterpreter(coroutineContext, clock)
+ val engine = FlowEngine(coroutineContext, clock)
val machine = SimBareMetalMachine(
- interpreter,
+ engine,
machineModel,
SimplePowerDriver(LinearPowerModel(100.0, 50.0))
)
- val source = SimPowerSource(interpreter, capacity = 1000.0)
+ val source = SimPowerSource(engine, capacity = 1000.0)
source.connect(machine.psu)
try {
@@ -125,7 +125,7 @@ class SimMachineTest {
@Test
fun testCapacityClamp() = runBlockingSimulation {
val machine = SimBareMetalMachine(
- SimResourceInterpreter(coroutineContext, clock),
+ FlowEngine(coroutineContext, clock),
machineModel,
SimplePowerDriver(ConstantPowerModel(0.0))
)
@@ -151,7 +151,7 @@ class SimMachineTest {
@Test
fun testMemory() = runBlockingSimulation {
val machine = SimBareMetalMachine(
- SimResourceInterpreter(coroutineContext, clock),
+ FlowEngine(coroutineContext, clock),
machineModel,
SimplePowerDriver(ConstantPowerModel(0.0))
)
@@ -171,7 +171,7 @@ class SimMachineTest {
@Test
fun testMemoryUsage() = runBlockingSimulation {
val machine = SimBareMetalMachine(
- SimResourceInterpreter(coroutineContext, clock),
+ FlowEngine(coroutineContext, clock),
machineModel,
SimplePowerDriver(ConstantPowerModel(0.0))
)
@@ -180,7 +180,7 @@ class SimMachineTest {
machine.run(object : SimWorkload {
override fun onStart(ctx: SimMachineContext) {
val lifecycle = SimWorkloadLifecycle(ctx)
- ctx.memory.startConsumer(lifecycle.waitFor(SimWorkConsumer(ctx.memory.capacity, utilization = 0.8)))
+ ctx.memory.startConsumer(lifecycle.waitFor(FixedFlowSource(ctx.memory.capacity, utilization = 0.8)))
}
})
@@ -192,22 +192,22 @@ class SimMachineTest {
@Test
fun testNetUsage() = runBlockingSimulation {
- val interpreter = SimResourceInterpreter(coroutineContext, clock)
+ val engine = FlowEngine(coroutineContext, clock)
val machine = SimBareMetalMachine(
- interpreter,
+ engine,
machineModel,
SimplePowerDriver(ConstantPowerModel(0.0))
)
val adapter = (machine.peripherals[0] as SimNetworkAdapter)
- adapter.connect(SimNetworkSink(interpreter, adapter.bandwidth))
+ adapter.connect(SimNetworkSink(engine, adapter.bandwidth))
try {
machine.run(object : SimWorkload {
override fun onStart(ctx: SimMachineContext) {
val lifecycle = SimWorkloadLifecycle(ctx)
val iface = ctx.net[0]
- iface.tx.startConsumer(lifecycle.waitFor(SimWorkConsumer(iface.bandwidth, utilization = 0.8)))
+ iface.tx.startConsumer(lifecycle.waitFor(FixedFlowSource(iface.bandwidth, utilization = 0.8)))
}
})
@@ -219,9 +219,9 @@ class SimMachineTest {
@Test
fun testDiskReadUsage() = runBlockingSimulation {
- val interpreter = SimResourceInterpreter(coroutineContext, clock)
+ val engine = FlowEngine(coroutineContext, clock)
val machine = SimBareMetalMachine(
- interpreter,
+ engine,
machineModel,
SimplePowerDriver(ConstantPowerModel(0.0))
)
@@ -231,7 +231,7 @@ class SimMachineTest {
override fun onStart(ctx: SimMachineContext) {
val lifecycle = SimWorkloadLifecycle(ctx)
val disk = ctx.storage[0]
- disk.read.startConsumer(lifecycle.waitFor(SimWorkConsumer(disk.read.capacity, utilization = 0.8)))
+ disk.read.startConsumer(lifecycle.waitFor(FixedFlowSource(disk.read.capacity, utilization = 0.8)))
}
})
@@ -243,9 +243,9 @@ class SimMachineTest {
@Test
fun testDiskWriteUsage() = runBlockingSimulation {
- val interpreter = SimResourceInterpreter(coroutineContext, clock)
+ val engine = FlowEngine(coroutineContext, clock)
val machine = SimBareMetalMachine(
- interpreter,
+ engine,
machineModel,
SimplePowerDriver(ConstantPowerModel(0.0))
)
@@ -255,7 +255,7 @@ class SimMachineTest {
override fun onStart(ctx: SimMachineContext) {
val lifecycle = SimWorkloadLifecycle(ctx)
val disk = ctx.storage[0]
- disk.write.startConsumer(lifecycle.waitFor(SimWorkConsumer(disk.write.capacity, utilization = 0.8)))
+ disk.write.startConsumer(lifecycle.waitFor(FixedFlowSource(disk.write.capacity, utilization = 0.8)))
}
})
@@ -268,7 +268,7 @@ class SimMachineTest {
@Test
fun testCancellation() = runBlockingSimulation {
val machine = SimBareMetalMachine(
- SimResourceInterpreter(coroutineContext, clock),
+ FlowEngine(coroutineContext, clock),
machineModel,
SimplePowerDriver(ConstantPowerModel(0.0))
)
@@ -290,7 +290,7 @@ class SimMachineTest {
@Test
fun testConcurrentRuns() = runBlockingSimulation {
val machine = SimBareMetalMachine(
- SimResourceInterpreter(coroutineContext, clock),
+ FlowEngine(coroutineContext, clock),
machineModel,
SimplePowerDriver(ConstantPowerModel(0.0))
)
@@ -313,7 +313,7 @@ class SimMachineTest {
@Test
fun testClose() = runBlockingSimulation {
val machine = SimBareMetalMachine(
- SimResourceInterpreter(coroutineContext, clock),
+ FlowEngine(coroutineContext, clock),
machineModel,
SimplePowerDriver(ConstantPowerModel(0.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
index 6c9ec7bd..e5b509f0 100644
--- 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
@@ -29,8 +29,8 @@ import org.junit.jupiter.api.Test
import org.junit.jupiter.api.assertThrows
import org.opendc.simulator.compute.power.PowerDriver
import org.opendc.simulator.core.runBlockingSimulation
+import org.opendc.simulator.flow.FlowEngine
import org.opendc.simulator.power.SimPowerSource
-import org.opendc.simulator.resources.SimResourceInterpreter
/**
* Test suite for [SimPsu]
@@ -55,8 +55,8 @@ internal class SimPsuTest {
val ratedOutputPower = 240.0
val energyEfficiency = mapOf(0.0 to 1.0)
- val interpreter = SimResourceInterpreter(coroutineContext, clock)
- val source = SimPowerSource(interpreter, capacity = ratedOutputPower)
+ val engine = FlowEngine(coroutineContext, clock)
+ val source = SimPowerSource(engine, capacity = ratedOutputPower)
val cpuLogic = mockk<PowerDriver.Logic>()
every { cpuLogic.computePower() } returns 0.0
@@ -78,8 +78,8 @@ internal class SimPsuTest {
1.0 to 0.94,
)
- val interpreter = SimResourceInterpreter(coroutineContext, clock)
- val source = SimPowerSource(interpreter, capacity = ratedOutputPower)
+ 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)
diff --git a/opendc-simulator/opendc-simulator-compute/src/test/kotlin/org/opendc/simulator/compute/kernel/SimHypervisorTest.kt b/opendc-simulator/opendc-simulator-compute/src/test/kotlin/org/opendc/simulator/compute/kernel/SimFairShareHypervisorTest.kt
index 1f010338..9db2e6ec 100644
--- a/opendc-simulator/opendc-simulator-compute/src/test/kotlin/org/opendc/simulator/compute/kernel/SimHypervisorTest.kt
+++ b/opendc-simulator/opendc-simulator-compute/src/test/kotlin/org/opendc/simulator/compute/kernel/SimFairShareHypervisorTest.kt
@@ -40,13 +40,13 @@ import org.opendc.simulator.compute.power.ConstantPowerModel
import org.opendc.simulator.compute.power.SimplePowerDriver
import org.opendc.simulator.compute.workload.SimTraceWorkload
import org.opendc.simulator.core.runBlockingSimulation
-import org.opendc.simulator.resources.SimResourceInterpreter
+import org.opendc.simulator.flow.FlowEngine
/**
* Test suite for the [SimHypervisor] class.
*/
@OptIn(ExperimentalCoroutinesApi::class)
-internal class SimHypervisorTest {
+internal class SimFairShareHypervisorTest {
private lateinit var model: MachineModel
@BeforeEach
@@ -63,26 +63,6 @@ internal class SimHypervisorTest {
*/
@Test
fun testOvercommittedSingle() = runBlockingSimulation {
- val listener = object : SimHypervisor.Listener {
- var totalRequestedWork = 0.0
- var totalGrantedWork = 0.0
- var totalOvercommittedWork = 0.0
-
- override fun onSliceFinish(
- hypervisor: SimHypervisor,
- totalWork: Double,
- grantedWork: Double,
- overcommittedWork: Double,
- interferedWork: Double,
- cpuUsage: Double,
- cpuDemand: Double
- ) {
- totalRequestedWork += totalWork
- totalGrantedWork += grantedWork
- totalOvercommittedWork += overcommittedWork
- }
- }
-
val duration = 5 * 60L
val workloadA =
SimTraceWorkload(
@@ -94,24 +74,26 @@ internal class SimHypervisorTest {
),
)
- val platform = SimResourceInterpreter(coroutineContext, clock)
+ val platform = FlowEngine(coroutineContext, clock)
val machine = SimBareMetalMachine(platform, model, SimplePowerDriver(ConstantPowerModel(0.0)))
- val hypervisor = SimFairShareHypervisor(platform, scalingGovernor = PerformanceScalingGovernor(), listener = listener)
+ val hypervisor = SimFairShareHypervisor(platform, null, PerformanceScalingGovernor(), null)
launch {
machine.run(hypervisor)
println("Hypervisor finished")
}
yield()
+
val vm = hypervisor.createMachine(model)
vm.run(workloadA)
+
yield()
machine.close()
assertAll(
- { assertEquals(1113300.0, listener.totalRequestedWork, "Requested Burst does not match") },
- { assertEquals(1023300.0, listener.totalGrantedWork, "Granted Burst does not match") },
- { assertEquals(90000.0, listener.totalOvercommittedWork, "Overcommissioned Burst does not match") },
+ { assertEquals(319781, hypervisor.counters.cpuActiveTime, "Active time does not match") },
+ { assertEquals(880219, hypervisor.counters.cpuIdleTime, "Idle time does not match") },
+ { assertEquals(28125, hypervisor.counters.cpuStealTime, "Steal time does not match") },
{ assertEquals(1200000, clock.millis()) { "Current time is correct" } }
)
}
@@ -121,26 +103,6 @@ internal class SimHypervisorTest {
*/
@Test
fun testOvercommittedDual() = runBlockingSimulation {
- val listener = object : SimHypervisor.Listener {
- var totalRequestedWork = 0.0
- var totalGrantedWork = 0.0
- var totalOvercommittedWork = 0.0
-
- override fun onSliceFinish(
- hypervisor: SimHypervisor,
- totalWork: Double,
- grantedWork: Double,
- overcommittedWork: Double,
- interferedWork: Double,
- cpuUsage: Double,
- cpuDemand: Double
- ) {
- totalRequestedWork += totalWork
- totalGrantedWork += grantedWork
- totalOvercommittedWork += overcommittedWork
- }
- }
-
val duration = 5 * 60L
val workloadA =
SimTraceWorkload(
@@ -161,11 +123,11 @@ internal class SimHypervisorTest {
)
)
- val platform = SimResourceInterpreter(coroutineContext, clock)
+ val platform = FlowEngine(coroutineContext, clock)
val machine = SimBareMetalMachine(
platform, model, SimplePowerDriver(ConstantPowerModel(0.0))
)
- val hypervisor = SimFairShareHypervisor(platform, listener = listener)
+ val hypervisor = SimFairShareHypervisor(platform, null, null, null)
launch {
machine.run(hypervisor)
@@ -187,9 +149,9 @@ internal class SimHypervisorTest {
yield()
assertAll(
- { assertEquals(2073600.0, listener.totalRequestedWork, "Requested Burst does not match") },
- { assertEquals(1053600.0, listener.totalGrantedWork, "Granted Burst does not match") },
- { assertEquals(1020000.0, listener.totalOvercommittedWork, "Overcommissioned Burst does not match") },
+ { assertEquals(329250, hypervisor.counters.cpuActiveTime, "Active time does not match") },
+ { assertEquals(870750, hypervisor.counters.cpuIdleTime, "Idle time does not match") },
+ { assertEquals(318750, hypervisor.counters.cpuStealTime, "Steal time does not match") },
{ assertEquals(1200000, clock.millis()) }
)
}
@@ -202,11 +164,9 @@ internal class SimHypervisorTest {
memory = List(4) { MemoryUnit("Crucial", "MTA18ASF4G72AZ-3G2B1", 3200.0, 32_000) }
)
- val platform = SimResourceInterpreter(coroutineContext, clock)
- val machine = SimBareMetalMachine(
- platform, model, SimplePowerDriver(ConstantPowerModel(0.0))
- )
- val hypervisor = SimFairShareHypervisor(platform)
+ val platform = FlowEngine(coroutineContext, clock)
+ val machine = SimBareMetalMachine(platform, model, SimplePowerDriver(ConstantPowerModel(0.0)))
+ val hypervisor = SimFairShareHypervisor(platform, null, null, null)
assertDoesNotThrow {
launch {
@@ -232,11 +192,11 @@ internal class SimHypervisorTest {
)
val interferenceModel = VmInterferenceModel(groups)
- val platform = SimResourceInterpreter(coroutineContext, clock)
+ val platform = FlowEngine(coroutineContext, clock)
val machine = SimBareMetalMachine(
platform, model, SimplePowerDriver(ConstantPowerModel(0.0))
)
- val hypervisor = SimFairShareHypervisor(platform, interferenceDomain = interferenceModel.newDomain())
+ val hypervisor = SimFairShareHypervisor(platform, null, null, interferenceModel.newDomain())
val duration = 5 * 60L
val workloadA =
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 3d3feb2a..b05ffd22 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
@@ -40,7 +40,7 @@ import org.opendc.simulator.compute.workload.SimFlopsWorkload
import org.opendc.simulator.compute.workload.SimRuntimeWorkload
import org.opendc.simulator.compute.workload.SimTraceWorkload
import org.opendc.simulator.core.runBlockingSimulation
-import org.opendc.simulator.resources.SimResourceInterpreter
+import org.opendc.simulator.flow.FlowEngine
/**
* A test suite for the [SimSpaceSharedHypervisor].
@@ -74,11 +74,9 @@ internal class SimSpaceSharedHypervisorTest {
),
)
- val interpreter = SimResourceInterpreter(coroutineContext, clock)
- val machine = SimBareMetalMachine(
- SimResourceInterpreter(coroutineContext, clock), machineModel, SimplePowerDriver(ConstantPowerModel(0.0))
- )
- val hypervisor = SimSpaceSharedHypervisor(interpreter)
+ val engine = FlowEngine(coroutineContext, clock)
+ val machine = SimBareMetalMachine(engine, machineModel, SimplePowerDriver(ConstantPowerModel(0.0)))
+ val hypervisor = SimSpaceSharedHypervisor(engine, null, null)
launch { machine.run(hypervisor) }
val vm = hypervisor.createMachine(machineModel)
@@ -98,11 +96,9 @@ internal class SimSpaceSharedHypervisorTest {
fun testRuntimeWorkload() = runBlockingSimulation {
val duration = 5 * 60L * 1000
val workload = SimRuntimeWorkload(duration)
- val interpreter = SimResourceInterpreter(coroutineContext, clock)
- val machine = SimBareMetalMachine(
- interpreter, machineModel, SimplePowerDriver(ConstantPowerModel(0.0))
- )
- val hypervisor = SimSpaceSharedHypervisor(interpreter)
+ val engine = FlowEngine(coroutineContext, clock)
+ val machine = SimBareMetalMachine(engine, machineModel, SimplePowerDriver(ConstantPowerModel(0.0)))
+ val hypervisor = SimSpaceSharedHypervisor(engine, null, null)
launch { machine.run(hypervisor) }
yield()
@@ -121,11 +117,11 @@ internal class SimSpaceSharedHypervisorTest {
fun testFlopsWorkload() = runBlockingSimulation {
val duration = 5 * 60L * 1000
val workload = SimFlopsWorkload((duration * 3.2).toLong(), 1.0)
- val interpreter = SimResourceInterpreter(coroutineContext, clock)
+ val engine = FlowEngine(coroutineContext, clock)
val machine = SimBareMetalMachine(
- interpreter, machineModel, SimplePowerDriver(ConstantPowerModel(0.0))
+ engine, machineModel, SimplePowerDriver(ConstantPowerModel(0.0))
)
- val hypervisor = SimSpaceSharedHypervisor(interpreter)
+ val hypervisor = SimSpaceSharedHypervisor(engine, null, null)
launch { machine.run(hypervisor) }
yield()
@@ -142,11 +138,11 @@ internal class SimSpaceSharedHypervisorTest {
@Test
fun testTwoWorkloads() = runBlockingSimulation {
val duration = 5 * 60L * 1000
- val interpreter = SimResourceInterpreter(coroutineContext, clock)
+ val engine = FlowEngine(coroutineContext, clock)
val machine = SimBareMetalMachine(
- interpreter, machineModel, SimplePowerDriver(ConstantPowerModel(0.0))
+ engine, machineModel, SimplePowerDriver(ConstantPowerModel(0.0))
)
- val hypervisor = SimSpaceSharedHypervisor(interpreter)
+ val hypervisor = SimSpaceSharedHypervisor(engine, null, null)
launch { machine.run(hypervisor) }
yield()
@@ -155,6 +151,8 @@ internal class SimSpaceSharedHypervisorTest {
vm.run(SimRuntimeWorkload(duration))
vm.close()
+ yield()
+
val vm2 = hypervisor.createMachine(machineModel)
vm2.run(SimRuntimeWorkload(duration))
vm2.close()
@@ -168,11 +166,9 @@ internal class SimSpaceSharedHypervisorTest {
*/
@Test
fun testConcurrentWorkloadFails() = runBlockingSimulation {
- val interpreter = SimResourceInterpreter(coroutineContext, clock)
- val machine = SimBareMetalMachine(
- interpreter, machineModel, SimplePowerDriver(ConstantPowerModel(0.0))
- )
- val hypervisor = SimSpaceSharedHypervisor(interpreter)
+ val engine = FlowEngine(coroutineContext, clock)
+ val machine = SimBareMetalMachine(engine, machineModel, SimplePowerDriver(ConstantPowerModel(0.0)))
+ val hypervisor = SimSpaceSharedHypervisor(engine, null, null)
launch { machine.run(hypervisor) }
yield()
@@ -192,11 +188,11 @@ internal class SimSpaceSharedHypervisorTest {
*/
@Test
fun testConcurrentWorkloadSucceeds() = runBlockingSimulation {
- val interpreter = SimResourceInterpreter(coroutineContext, clock)
+ val interpreter = FlowEngine(coroutineContext, clock)
val machine = SimBareMetalMachine(
interpreter, machineModel, SimplePowerDriver(ConstantPowerModel(0.0))
)
- val hypervisor = SimSpaceSharedHypervisor(interpreter)
+ val hypervisor = SimSpaceSharedHypervisor(interpreter, null, null)
launch { machine.run(hypervisor) }
yield()
diff --git a/opendc-simulator/opendc-simulator-compute/src/test/kotlin/org/opendc/simulator/compute/power/PStatePowerDriverTest.kt b/opendc-simulator/opendc-simulator-compute/src/test/kotlin/org/opendc/simulator/compute/power/PStatePowerDriverTest.kt
index c39859bf..f557c8d3 100644
--- 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
@@ -55,7 +55,7 @@ internal class PStatePowerDriverTest {
val cpu = mockk<SimProcessingUnit>(relaxUnitFun = true)
every { cpu.capacity } returns 3200.0
- every { cpu.speed } returns 1200.0
+ every { cpu.rate } returns 1200.0
val driver = PStatePowerDriver(
sortedMapOf(
@@ -77,10 +77,10 @@ internal class PStatePowerDriverTest {
val cpus = listOf(cpu, cpu)
every { cpus[0].capacity } returns 1000.0
- every { cpus[0].speed } returns 1200.0
+ every { cpus[0].rate } returns 1200.0
every { cpus[1].capacity } returns 3500.0
- every { cpus[1].speed } returns 1200.0
+ every { cpus[1].rate } returns 1200.0
val driver = PStatePowerDriver(
sortedMapOf(
@@ -112,11 +112,11 @@ internal class PStatePowerDriverTest {
val logic = driver.createLogic(machine, listOf(cpu))
- every { cpu.speed } returns 1400.0
+ every { cpu.rate } returns 1400.0
every { cpu.capacity } returns 1400.0
assertEquals(150.0, logic.computePower())
- every { cpu.speed } returns 1400.0
+ 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/workload/SimTraceWorkloadTest.kt b/opendc-simulator/opendc-simulator-compute/src/test/kotlin/org/opendc/simulator/compute/workload/SimTraceWorkloadTest.kt
index 78019c2e..cdbffe4b 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,7 +31,7 @@ import org.opendc.simulator.compute.model.*
import org.opendc.simulator.compute.power.ConstantPowerModel
import org.opendc.simulator.compute.power.SimplePowerDriver
import org.opendc.simulator.core.runBlockingSimulation
-import org.opendc.simulator.resources.SimResourceInterpreter
+import org.opendc.simulator.flow.FlowEngine
/**
* Test suite for the [SimTraceWorkloadTest] class.
@@ -52,7 +52,7 @@ class SimTraceWorkloadTest {
@Test
fun testSmoke() = runBlockingSimulation {
val machine = SimBareMetalMachine(
- SimResourceInterpreter(coroutineContext, clock),
+ FlowEngine(coroutineContext, clock),
machineModel,
SimplePowerDriver(ConstantPowerModel(0.0))
)
@@ -79,7 +79,7 @@ class SimTraceWorkloadTest {
@Test
fun testOffset() = runBlockingSimulation {
val machine = SimBareMetalMachine(
- SimResourceInterpreter(coroutineContext, clock),
+ FlowEngine(coroutineContext, clock),
machineModel,
SimplePowerDriver(ConstantPowerModel(0.0))
)
@@ -106,7 +106,7 @@ class SimTraceWorkloadTest {
@Test
fun testSkipFragment() = runBlockingSimulation {
val machine = SimBareMetalMachine(
- SimResourceInterpreter(coroutineContext, clock),
+ FlowEngine(coroutineContext, clock),
machineModel,
SimplePowerDriver(ConstantPowerModel(0.0))
)
@@ -134,7 +134,7 @@ class SimTraceWorkloadTest {
@Test
fun testZeroCores() = runBlockingSimulation {
val machine = SimBareMetalMachine(
- SimResourceInterpreter(coroutineContext, clock),
+ FlowEngine(coroutineContext, clock),
machineModel,
SimplePowerDriver(ConstantPowerModel(0.0))
)
diff --git a/opendc-simulator/opendc-simulator-resources/build.gradle.kts b/opendc-simulator/opendc-simulator-flow/build.gradle.kts
index e4ffc3ff..05e21c3c 100644
--- a/opendc-simulator/opendc-simulator-resources/build.gradle.kts
+++ b/opendc-simulator/opendc-simulator-flow/build.gradle.kts
@@ -20,7 +20,7 @@
* SOFTWARE.
*/
-description = "Uniform resource consumption simulation model"
+description = "High-performance flow simulator"
plugins {
`kotlin-library-conventions`
@@ -32,8 +32,8 @@ plugins {
dependencies {
api(platform(projects.opendcPlatform))
api(libs.kotlinx.coroutines)
- implementation(projects.opendcUtils)
+ implementation(libs.kotlin.logging)
- jmhImplementation(projects.opendcSimulator.opendcSimulatorCore)
testImplementation(projects.opendcSimulator.opendcSimulatorCore)
+ testImplementation(libs.slf4j.simple)
}
diff --git a/opendc-simulator/opendc-simulator-flow/src/jmh/kotlin/org/opendc/simulator/flow/FlowBenchmarks.kt b/opendc-simulator/opendc-simulator-flow/src/jmh/kotlin/org/opendc/simulator/flow/FlowBenchmarks.kt
new file mode 100644
index 00000000..e927f81d
--- /dev/null
+++ b/opendc-simulator/opendc-simulator-flow/src/jmh/kotlin/org/opendc/simulator/flow/FlowBenchmarks.kt
@@ -0,0 +1,140 @@
+/*
+ * Copyright (c) 2021 AtLarge Research
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
+ * of this software and associated documentation files (the "Software"), to deal
+ * in the Software without restriction, including without limitation the rights
+ * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+ * copies of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included in all
+ * copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
+ * SOFTWARE.
+ */
+
+package org.opendc.simulator.flow
+
+import kotlinx.coroutines.ExperimentalCoroutinesApi
+import kotlinx.coroutines.launch
+import org.opendc.simulator.core.SimulationCoroutineScope
+import org.opendc.simulator.core.runBlockingSimulation
+import org.opendc.simulator.flow.mux.ForwardingFlowMultiplexer
+import org.opendc.simulator.flow.mux.MaxMinFlowMultiplexer
+import org.opendc.simulator.flow.source.TraceFlowSource
+import org.openjdk.jmh.annotations.*
+import java.util.concurrent.ThreadLocalRandom
+import java.util.concurrent.TimeUnit
+
+@State(Scope.Thread)
+@Fork(1)
+@Warmup(iterations = 2, time = 1, timeUnit = TimeUnit.SECONDS)
+@Measurement(iterations = 5, time = 3, timeUnit = TimeUnit.SECONDS)
+@OptIn(ExperimentalCoroutinesApi::class)
+class FlowBenchmarks {
+ private lateinit var scope: SimulationCoroutineScope
+ private lateinit var engine: FlowEngine
+
+ @Setup
+ fun setUp() {
+ scope = SimulationCoroutineScope()
+ engine = FlowEngine(scope.coroutineContext, scope.clock)
+ }
+
+ @State(Scope.Thread)
+ class Workload {
+ 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(state: Workload) {
+ return scope.runBlockingSimulation {
+ val provider = FlowSink(engine, 4200.0)
+ return@runBlockingSimulation provider.consume(TraceFlowSource(state.trace))
+ }
+ }
+
+ @Benchmark
+ fun benchmarkForward(state: Workload) {
+ return scope.runBlockingSimulation {
+ val provider = FlowSink(engine, 4200.0)
+ val forwarder = FlowForwarder(engine)
+ provider.startConsumer(forwarder)
+ return@runBlockingSimulation forwarder.consume(TraceFlowSource(state.trace))
+ }
+ }
+
+ @Benchmark
+ fun benchmarkMuxMaxMinSingleSource(state: Workload) {
+ return scope.runBlockingSimulation {
+ val switch = MaxMinFlowMultiplexer(engine)
+
+ FlowSink(engine, 3000.0).startConsumer(switch.newOutput())
+ FlowSink(engine, 3000.0).startConsumer(switch.newOutput())
+
+ val provider = switch.newInput()
+ return@runBlockingSimulation provider.consume(TraceFlowSource(state.trace))
+ }
+ }
+
+ @Benchmark
+ fun benchmarkMuxMaxMinTripleSource(state: Workload) {
+ return scope.runBlockingSimulation {
+ 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(state.trace))
+ }
+ }
+ }
+ }
+
+ @Benchmark
+ fun benchmarkMuxExclusiveSingleSource(state: Workload) {
+ return scope.runBlockingSimulation {
+ val switch = ForwardingFlowMultiplexer(engine)
+
+ FlowSink(engine, 3000.0).startConsumer(switch.newOutput())
+ FlowSink(engine, 3000.0).startConsumer(switch.newOutput())
+
+ val provider = switch.newInput()
+ return@runBlockingSimulation provider.consume(TraceFlowSource(state.trace))
+ }
+ }
+
+ @Benchmark
+ fun benchmarkMuxExclusiveTripleSource(state: Workload) {
+ return scope.runBlockingSimulation {
+ 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(state.trace))
+ }
+ }
+ }
+ }
+}
diff --git a/opendc-simulator/opendc-simulator-flow/src/main/kotlin/org/opendc/simulator/flow/AbstractFlowConsumer.kt b/opendc-simulator/opendc-simulator-flow/src/main/kotlin/org/opendc/simulator/flow/AbstractFlowConsumer.kt
new file mode 100644
index 00000000..b02426e3
--- /dev/null
+++ b/opendc-simulator/opendc-simulator-flow/src/main/kotlin/org/opendc/simulator/flow/AbstractFlowConsumer.kt
@@ -0,0 +1,147 @@
+/*
+ * 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.FlowCountersImpl
+
+/**
+ * Abstract implementation of the [FlowConsumer] which can be re-used by other implementations.
+ */
+public abstract class AbstractFlowConsumer(private val engine: FlowEngine, initialCapacity: Double) : 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 = FlowCountersImpl()
+
+ /**
+ * The [FlowConsumerContext] that is currently running.
+ */
+ protected var ctx: FlowConsumerContext? = null
+ private set
+
+ /**
+ * Construct the [FlowConsumerLogic] instance for a new source.
+ */
+ protected abstract fun createLogic(): FlowConsumerLogic
+
+ /**
+ * Start the specified [FlowConsumerContext].
+ */
+ protected open fun start(ctx: FlowConsumerContext) {
+ ctx.start()
+ }
+
+ /**
+ * The previous demand for the consumer.
+ */
+ private var _previousDemand = 0.0
+ private var _previousCapacity = 0.0
+
+ /**
+ * Update the counters of the flow consumer.
+ */
+ protected fun updateCounters(ctx: FlowConnection, delta: Long) {
+ val demand = _previousDemand
+ val capacity = _previousCapacity
+
+ _previousDemand = ctx.demand
+ _previousCapacity = ctx.capacity
+
+ if (delta <= 0) {
+ return
+ }
+
+ val counters = _counters
+ val deltaS = delta / 1000.0
+ val total = demand * deltaS
+ val work = capacity * deltaS
+ val actualWork = ctx.rate * deltaS
+
+ counters.demand += work
+ counters.actual += actualWork
+ counters.remaining += (total - actualWork)
+ }
+
+ /**
+ * Update the counters of the flow consumer.
+ */
+ protected fun updateCounters(demand: Double, actual: Double, remaining: Double) {
+ val counters = _counters
+ counters.demand += demand
+ counters.actual += actual
+ counters.remaining += remaining
+ }
+
+ final override fun startConsumer(source: FlowSource) {
+ check(ctx == null) { "Consumer is in invalid state" }
+ val ctx = engine.newContext(source, createLogic())
+
+ ctx.capacity = capacity
+ this.ctx = ctx
+
+ start(ctx)
+ }
+
+ final override fun pull() {
+ ctx?.pull()
+ }
+
+ final override fun cancel() {
+ val ctx = ctx
+ if (ctx != null) {
+ this.ctx = null
+ ctx.close()
+ }
+ }
+
+ override fun toString(): String = "AbstractFlowConsumer[capacity=$capacity]"
+}
diff --git a/opendc-simulator/opendc-simulator-resources/src/main/kotlin/org/opendc/simulator/resources/SimResourceContext.kt b/opendc-simulator/opendc-simulator-flow/src/main/kotlin/org/opendc/simulator/flow/FlowConnection.kt
index 0d9a6106..c327e1e9 100644
--- a/opendc-simulator/opendc-simulator-resources/src/main/kotlin/org/opendc/simulator/resources/SimResourceContext.kt
+++ b/opendc-simulator/opendc-simulator-flow/src/main/kotlin/org/opendc/simulator/flow/FlowConnection.kt
@@ -20,42 +20,46 @@
* SOFTWARE.
*/
-package org.opendc.simulator.resources
-
-import java.time.Clock
+package org.opendc.simulator.flow
/**
- * The execution context in which a [SimResourceConsumer] runs. It facilitates the communication and control between a
- * resource and a resource consumer.
+ * An active connection between a [FlowSource] and [FlowConsumer].
*/
-public interface SimResourceContext {
+public interface FlowConnection : AutoCloseable {
/**
- * The virtual clock tracking simulation time.
+ * The capacity of the connection.
*/
- public val clock: Clock
+ public val capacity: Double
/**
- * The resource capacity available at this instant.
+ * The flow rate over the connection.
*/
- public val capacity: Double
+ public val rate: Double
+
+ /**
+ * The flow demand of the source.
+ */
+ public val demand: Double
/**
- * The resource processing speed at this instant.
+ * A flag to control whether [FlowSource.onConverge] should be invoked for this source.
*/
- public val speed: Double
+ public var shouldSourceConverge: Boolean
/**
- * The resource processing speed demand at this instant.
+ * Pull the source.
*/
- public val demand: Double
+ public fun pull()
/**
- * The amount of work still remaining at this instant.
+ * Push the given flow [rate] over this connection.
+ *
+ * @param rate The rate of the flow to push.
*/
- public val remainingWork: Double
+ public fun push(rate: Double)
/**
- * Ask the resource provider to interrupt its resource.
+ * Disconnect the consumer from its source.
*/
- public fun interrupt()
+ public override fun close()
}
diff --git a/opendc-simulator/opendc-simulator-flow/src/main/kotlin/org/opendc/simulator/flow/FlowConsumer.kt b/opendc-simulator/opendc-simulator-flow/src/main/kotlin/org/opendc/simulator/flow/FlowConsumer.kt
new file mode 100644
index 00000000..4685a755
--- /dev/null
+++ b/opendc-simulator/opendc-simulator-flow/src/main/kotlin/org/opendc/simulator/flow/FlowConsumer.kt
@@ -0,0 +1,131 @@
+/*
+ * Copyright (c) 2021 AtLarge Research
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
+ * of this software and associated documentation files (the "Software"), to deal
+ * in the Software without restriction, including without limitation the rights
+ * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+ * copies of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included in all
+ * copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
+ * SOFTWARE.
+ */
+
+package org.opendc.simulator.flow
+
+import kotlinx.coroutines.suspendCancellableCoroutine
+import kotlin.coroutines.resume
+import kotlin.coroutines.resumeWithException
+
+/**
+ * A consumer of a [FlowSource].
+ */
+public interface FlowConsumer {
+ /**
+ * A flag to indicate that the consumer is currently consuming a [FlowSource].
+ */
+ public val isActive: Boolean
+
+ /**
+ * The flow capacity of this consumer.
+ */
+ public val capacity: Double
+
+ /**
+ * The current flow rate of the consumer.
+ */
+ public val rate: Double
+
+ /**
+ * The current flow demand.
+ */
+ public val demand: Double
+
+ /**
+ * The flow counters to track the flow metrics of the consumer.
+ */
+ public val counters: FlowCounters
+
+ /**
+ * Start consuming the specified [source].
+ *
+ * @throws IllegalStateException if the consumer is already active.
+ */
+ public fun startConsumer(source: FlowSource)
+
+ /**
+ * Ask the consumer to pull its source.
+ *
+ * If the consumer is not active, this operation will be a no-op.
+ */
+ public fun pull()
+
+ /**
+ * Disconnect the consumer from its source.
+ *
+ * If the consumer is not active, this operation will be a no-op.
+ */
+ public fun cancel()
+}
+
+/**
+ * Consume the specified [source] and suspend execution until the source is fully consumed or failed.
+ */
+public suspend fun FlowConsumer.consume(source: FlowSource) {
+ return suspendCancellableCoroutine { cont ->
+ startConsumer(object : FlowSource {
+ override fun onStart(conn: FlowConnection, now: Long) {
+ try {
+ source.onStart(conn, now)
+ } catch (cause: Throwable) {
+ cont.resumeWithException(cause)
+ throw cause
+ }
+ }
+
+ override fun onStop(conn: FlowConnection, now: Long, delta: Long) {
+ try {
+ source.onStop(conn, now, delta)
+
+ if (!cont.isCompleted) {
+ cont.resume(Unit)
+ }
+ } catch (cause: Throwable) {
+ cont.resumeWithException(cause)
+ throw cause
+ }
+ }
+
+ override fun onPull(conn: FlowConnection, now: Long, delta: Long): Long {
+ return try {
+ source.onPull(conn, now, delta)
+ } catch (cause: Throwable) {
+ cont.resumeWithException(cause)
+ throw cause
+ }
+ }
+
+ override fun onConverge(conn: FlowConnection, now: Long, delta: Long) {
+ try {
+ source.onConverge(conn, now, delta)
+ } catch (cause: Throwable) {
+ cont.resumeWithException(cause)
+ throw cause
+ }
+ }
+
+ override fun toString(): String = "SuspendingFlowSource"
+ })
+
+ cont.invokeOnCancellation { cancel() }
+ }
+}
diff --git a/opendc-simulator/opendc-simulator-resources/src/main/kotlin/org/opendc/simulator/resources/SimResourceState.kt b/opendc-simulator/opendc-simulator-flow/src/main/kotlin/org/opendc/simulator/flow/FlowConsumerContext.kt
index c72951d0..15f9b93b 100644
--- a/opendc-simulator/opendc-simulator-resources/src/main/kotlin/org/opendc/simulator/resources/SimResourceState.kt
+++ b/opendc-simulator/opendc-simulator-flow/src/main/kotlin/org/opendc/simulator/flow/FlowConsumerContext.kt
@@ -20,24 +20,31 @@
* SOFTWARE.
*/
-package org.opendc.simulator.resources
+package org.opendc.simulator.flow
/**
- * The state of a resource provider.
+ * A controllable [FlowConnection].
+ *
+ * This interface is used by [FlowConsumer]s to control the connection between it and the source.
*/
-public enum class SimResourceState {
+public interface FlowConsumerContext : FlowConnection {
+ /**
+ * The capacity of the connection.
+ */
+ public override var capacity: Double
+
/**
- * The resource provider is pending and the resource is waiting to be consumed.
+ * A flag to control whether [FlowConsumerLogic.onConverge] should be invoked for the consumer.
*/
- Pending,
+ public var shouldConsumerConverge: Boolean
/**
- * The resource provider is active and the resource is currently being consumed.
+ * Start the flow over the connection.
*/
- Active,
+ public fun start()
/**
- * The resource provider is stopped and the resource cannot be consumed anymore.
+ * Synchronously flush the changes of the connection.
*/
- Stopped
+ public fun flush()
}
diff --git a/opendc-simulator/opendc-simulator-flow/src/main/kotlin/org/opendc/simulator/flow/FlowConsumerLogic.kt b/opendc-simulator/opendc-simulator-flow/src/main/kotlin/org/opendc/simulator/flow/FlowConsumerLogic.kt
new file mode 100644
index 00000000..50fbc9c7
--- /dev/null
+++ b/opendc-simulator/opendc-simulator-flow/src/main/kotlin/org/opendc/simulator/flow/FlowConsumerLogic.kt
@@ -0,0 +1,60 @@
+/*
+ * Copyright (c) 2021 AtLarge Research
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
+ * of this software and associated documentation files (the "Software"), to deal
+ * in the Software without restriction, including without limitation the rights
+ * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+ * copies of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included in all
+ * copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
+ * SOFTWARE.
+ */
+
+package org.opendc.simulator.flow
+
+/**
+ * A collection of callbacks associated with a [FlowConsumer].
+ */
+public interface FlowConsumerLogic {
+ /**
+ * This method is invoked when a [FlowSource] changes the rate of flow to this consumer.
+ *
+ * @param ctx The context in which the provider runs.
+ * @param now The virtual timestamp in milliseconds at which the update is occurring.
+ * @param delta The virtual duration between this call and the last call to [onPush] in milliseconds.
+ * @param rate The requested processing rate of the source.
+ */
+ public fun onPush(ctx: FlowConsumerContext, now: Long, delta: Long, rate: Double) {}
+
+ /**
+ * This method is invoked when the flow graph has converged into a steady-state system.
+ *
+ * Make sure to enable [FlowConsumerContext.shouldSourceConverge] if you need this callback. By default, this method
+ * will not be invoked.
+ *
+ * @param ctx The context in which the provider runs.
+ * @param now The virtual timestamp in milliseconds at which the system converged.
+ * @param delta The virtual duration between this call and the last call to [onConverge] in milliseconds.
+ */
+ public fun onConverge(ctx: FlowConsumerContext, now: Long, delta: Long) {}
+
+ /**
+ * This method is invoked when the [FlowSource] completed or failed.
+ *
+ * @param ctx The context in which the provider runs.
+ * @param now The virtual timestamp in milliseconds at which the provider finished.
+ * @param delta The virtual duration between this call and the last call to [onPush] in milliseconds.
+ * @param cause The cause of the failure or `null` if the source completed.
+ */
+ public fun onFinish(ctx: FlowConsumerContext, now: Long, delta: Long, cause: Throwable?) {}
+}
diff --git a/opendc-simulator/opendc-simulator-resources/src/main/kotlin/org/opendc/simulator/resources/SimResourceAggregator.kt b/opendc-simulator/opendc-simulator-flow/src/main/kotlin/org/opendc/simulator/flow/FlowConvergenceListener.kt
index 00972f43..d1afda6f 100644
--- a/opendc-simulator/opendc-simulator-resources/src/main/kotlin/org/opendc/simulator/resources/SimResourceAggregator.kt
+++ b/opendc-simulator/opendc-simulator-flow/src/main/kotlin/org/opendc/simulator/flow/FlowConvergenceListener.kt
@@ -20,19 +20,17 @@
* SOFTWARE.
*/
-package org.opendc.simulator.resources
+package org.opendc.simulator.flow
/**
- * A [SimResourceAggregator] aggregates the capacity of multiple resources into a single resource.
+ * A listener interface for when a flow stage has converged into a steady-state.
*/
-public interface SimResourceAggregator : SimResourceProvider {
+public interface FlowConvergenceListener {
/**
- * The input resources that will be switched between the output providers.
+ * This method is invoked when the system has converged to a steady-state.
+ *
+ * @param now The timestamp at which the system converged.
+ * @param delta The virtual duration between this call and the last call to [onConverge] in milliseconds.
*/
- public val inputs: Set<SimResourceProvider>
-
- /**
- * Add the specified [input] to the aggregator.
- */
- public fun addInput(input: SimResourceProvider)
+ public fun onConverge(now: Long, delta: Long) {}
}
diff --git a/opendc-simulator/opendc-simulator-resources/src/main/kotlin/org/opendc/simulator/resources/SimResourceCounters.kt b/opendc-simulator/opendc-simulator-flow/src/main/kotlin/org/opendc/simulator/flow/FlowCounters.kt
index 725aa5bc..a717ae6e 100644
--- a/opendc-simulator/opendc-simulator-resources/src/main/kotlin/org/opendc/simulator/resources/SimResourceCounters.kt
+++ b/opendc-simulator/opendc-simulator-flow/src/main/kotlin/org/opendc/simulator/flow/FlowCounters.kt
@@ -20,29 +20,34 @@
* SOFTWARE.
*/
-package org.opendc.simulator.resources
+package org.opendc.simulator.flow
/**
- * An interface that tracks cumulative counts of the work performed by a resource.
+ * An interface that tracks cumulative counts of the flow accumulation over a stage.
*/
-public interface SimResourceCounters {
+public interface FlowCounters {
/**
- * The amount of work that resource consumers wanted the resource to perform.
+ * The accumulated flow that a source wanted to push over the connection.
*/
public val demand: Double
/**
- * The amount of work performed by the resource.
+ * The accumulated flow that was actually transferred over the connection.
*/
public val actual: Double
/**
- * The amount of work that could not be completed due to overcommitted resources.
+ * The amount of capacity that was not utilized.
*/
- public val overcommit: Double
+ public val remaining: Double
/**
- * Reset the resource counters.
+ * The accumulated flow lost due to interference between sources.
+ */
+ public val interference: Double
+
+ /**
+ * Reset the flow counters.
*/
public fun reset()
}
diff --git a/opendc-simulator/opendc-simulator-resources/src/main/kotlin/org/opendc/simulator/resources/SimResourceInterpreter.kt b/opendc-simulator/opendc-simulator-flow/src/main/kotlin/org/opendc/simulator/flow/FlowEngine.kt
index 82631377..65224827 100644
--- a/opendc-simulator/opendc-simulator-resources/src/main/kotlin/org/opendc/simulator/resources/SimResourceInterpreter.kt
+++ b/opendc-simulator/opendc-simulator-flow/src/main/kotlin/org/opendc/simulator/flow/FlowEngine.kt
@@ -20,36 +20,31 @@
* SOFTWARE.
*/
-package org.opendc.simulator.resources
+package org.opendc.simulator.flow
-import org.opendc.simulator.resources.impl.SimResourceInterpreterImpl
+import org.opendc.simulator.flow.internal.FlowEngineImpl
import java.time.Clock
import kotlin.coroutines.CoroutineContext
/**
- * The resource interpreter is responsible for managing the interaction between resource consumer and provider.
+ * A [FlowEngine] is responsible for managing the interaction between [FlowSource]s and [FlowConsumer]s.
*
- * The interpreter centralizes the scheduling logic of state updates of resource context, allowing update propagation
+ * 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 SimResourceInterpreter {
+public interface FlowEngine {
/**
- * The [Clock] associated with this interpreter.
+ * The virtual [Clock] associated with this engine.
*/
public val clock: Clock
/**
- * Create a new [SimResourceControllableContext] with the given [provider].
+ * Create a new [FlowConsumerContext] with the given [provider].
*
* @param consumer The consumer logic.
* @param provider The logic of the resource provider.
- * @param parent The system to which the resource context belongs.
*/
- public fun newContext(
- consumer: SimResourceConsumer,
- provider: SimResourceProviderLogic,
- parent: SimResourceSystem? = null
- ): SimResourceControllableContext
+ public fun newContext(consumer: FlowSource, provider: FlowConsumerLogic): FlowConsumerContext
/**
* Start batching the execution of resource updates until [popBatch] is called.
@@ -72,14 +67,15 @@ public interface SimResourceInterpreter {
public companion object {
/**
- * Construct a new [SimResourceInterpreter] implementation.
+ * 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): SimResourceInterpreter {
- return SimResourceInterpreterImpl(context, clock)
+ public operator fun invoke(context: CoroutineContext, clock: Clock): FlowEngine {
+ return FlowEngineImpl(context, clock)
}
}
}
@@ -89,7 +85,7 @@ public interface SimResourceInterpreter {
*
* This method is useful if you want to propagate the start of multiple resources (e.g., CPUs) in a single update.
*/
-public inline fun SimResourceInterpreter.batch(block: () -> Unit) {
+public inline fun FlowEngine.batch(block: () -> Unit) {
try {
pushBatch()
block()
diff --git a/opendc-simulator/opendc-simulator-flow/src/main/kotlin/org/opendc/simulator/flow/FlowForwarder.kt b/opendc-simulator/opendc-simulator-flow/src/main/kotlin/org/opendc/simulator/flow/FlowForwarder.kt
new file mode 100644
index 00000000..7eaaf6c2
--- /dev/null
+++ b/opendc-simulator/opendc-simulator-flow/src/main/kotlin/org/opendc/simulator/flow/FlowForwarder.kt
@@ -0,0 +1,252 @@
+/*
+ * 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.FlowCountersImpl
+import kotlin.math.max
+
+/**
+ * A class that acts as a [FlowSource] and [FlowConsumer] at the same time.
+ *
+ * @param engine The [FlowEngine] the forwarder runs in.
+ * @param isCoupled A flag to indicate that the transformer will exit when the resource consumer exits.
+ */
+public class FlowForwarder(private val engine: FlowEngine, private val isCoupled: Boolean = false) : FlowSource, FlowConsumer, AutoCloseable {
+ /**
+ * The logging instance of this connection.
+ */
+ private val logger = KotlinLogging.logger {}
+
+ /**
+ * The delegate [FlowSource].
+ */
+ private var delegate: FlowSource? = null
+
+ /**
+ * A flag to indicate that the delegate was started.
+ */
+ private var hasDelegateStarted: Boolean = false
+
+ /**
+ * The exposed [FlowConnection].
+ */
+ private val _ctx = object : FlowConnection {
+ override var shouldSourceConverge: Boolean = false
+ set(value) {
+ field = value
+ _innerCtx?.shouldSourceConverge = value
+ }
+
+ override val capacity: Double
+ get() = _innerCtx?.capacity ?: 0.0
+
+ override val demand: Double
+ get() = _innerCtx?.demand ?: 0.0
+
+ override val rate: Double
+ get() = _innerCtx?.rate ?: 0.0
+
+ override fun pull() {
+ _innerCtx?.pull()
+ }
+
+ @JvmField var lastPull = Long.MAX_VALUE
+
+ override fun push(rate: Double) {
+ if (delegate == null) {
+ return
+ }
+
+ _innerCtx?.push(rate)
+ _demand = rate
+ }
+
+ override fun close() {
+ val delegate = delegate ?: return
+ val hasDelegateStarted = hasDelegateStarted
+
+ // Warning: resumption of the continuation might change the entire state of the forwarder. Make sure we
+ // reset beforehand the existing state and check whether it has been updated afterwards
+ reset()
+
+ if (hasDelegateStarted) {
+ val now = engine.clock.millis()
+ val delta = max(0, now - lastPull)
+ delegate.onStop(this, now, delta)
+ }
+ }
+ }
+
+ /**
+ * The [FlowConnection] in which the forwarder runs.
+ */
+ private var _innerCtx: FlowConnection? = null
+
+ override val isActive: Boolean
+ get() = delegate != null
+
+ override val capacity: Double
+ get() = _ctx.capacity
+
+ override val rate: Double
+ get() = _ctx.rate
+
+ override val demand: Double
+ get() = _ctx.demand
+
+ override val counters: FlowCounters
+ get() = _counters
+ private val _counters = FlowCountersImpl()
+
+ override fun startConsumer(source: FlowSource) {
+ check(delegate == null) { "Forwarder already active" }
+
+ delegate = source
+
+ // Pull to replace the source
+ pull()
+ }
+
+ override fun pull() {
+ _ctx.pull()
+ }
+
+ override fun cancel() {
+ _ctx.close()
+ }
+
+ override fun close() {
+ val ctx = _innerCtx
+
+ if (ctx != null) {
+ this._innerCtx = null
+ ctx.pull()
+ }
+ }
+
+ override fun onStart(conn: FlowConnection, now: Long) {
+ _innerCtx = conn
+
+ if (_ctx.shouldSourceConverge) {
+ conn.shouldSourceConverge = true
+ }
+ }
+
+ override fun onStop(conn: FlowConnection, now: Long, delta: Long) {
+ _innerCtx = null
+
+ val delegate = delegate
+ if (delegate != null) {
+ reset()
+
+ try {
+ delegate.onStop(this._ctx, now, delta)
+ } catch (cause: Throwable) {
+ logger.error(cause) { "Uncaught exception" }
+ }
+ }
+ }
+
+ override fun onPull(conn: FlowConnection, now: Long, delta: Long): Long {
+ val delegate = delegate
+
+ if (!hasDelegateStarted) {
+ start()
+ }
+
+ _ctx.lastPull = now
+ updateCounters(conn, delta)
+
+ return try {
+ delegate?.onPull(_ctx, now, delta) ?: Long.MAX_VALUE
+ } catch (cause: Throwable) {
+ logger.error(cause) { "Uncaught exception" }
+
+ reset()
+ Long.MAX_VALUE
+ }
+ }
+
+ override fun onConverge(conn: FlowConnection, now: Long, delta: Long) {
+ try {
+ delegate?.onConverge(this._ctx, now, delta)
+ } catch (cause: Throwable) {
+ logger.error(cause) { "Uncaught exception" }
+
+ _innerCtx = null
+ reset()
+ }
+ }
+
+ /**
+ * Start the delegate.
+ */
+ private fun start() {
+ val delegate = delegate ?: return
+
+ try {
+ delegate.onStart(_ctx, engine.clock.millis())
+ hasDelegateStarted = true
+ } catch (cause: Throwable) {
+ logger.error(cause) { "Uncaught exception" }
+ reset()
+ }
+ }
+
+ /**
+ * Reset the delegate.
+ */
+ private fun reset() {
+ if (isCoupled)
+ _innerCtx?.close()
+ else
+ _innerCtx?.push(0.0)
+
+ delegate = null
+ hasDelegateStarted = false
+ }
+
+ /**
+ * The requested flow rate.
+ */
+ private var _demand: Double = 0.0
+
+ /**
+ * Update the flow counters for the transformer.
+ */
+ private fun updateCounters(ctx: FlowConnection, delta: Long) {
+ if (delta <= 0) {
+ return
+ }
+
+ val counters = _counters
+ val deltaS = delta / 1000.0
+ val total = ctx.capacity * deltaS
+ val work = _demand * deltaS
+ val actualWork = ctx.rate * deltaS
+ counters.demand += work
+ counters.actual += actualWork
+ counters.remaining += (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
new file mode 100644
index 00000000..6867bcef
--- /dev/null
+++ b/opendc-simulator/opendc-simulator-flow/src/main/kotlin/org/opendc/simulator/flow/FlowMapper.kt
@@ -0,0 +1,75 @@
+/*
+ * Copyright (c) 2021 AtLarge Research
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
+ * of this software and associated documentation files (the "Software"), to deal
+ * in the Software without restriction, including without limitation the rights
+ * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+ * copies of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included in all
+ * copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
+ * SOFTWARE.
+ */
+
+package org.opendc.simulator.flow
+
+/**
+ * A [FlowConsumer] that maps the pushed flow through [transform].
+ *
+ * @param source The source of the flow.
+ * @param transform The method to transform the flow.
+ */
+public class FlowMapper(
+ private val source: FlowSource,
+ private val transform: (FlowConnection, Double) -> Double
+) : FlowSource {
+
+ /**
+ * The current active connection.
+ */
+ private var _conn: Connection? = null
+
+ override fun onStart(conn: FlowConnection, now: Long) {
+ check(_conn == null) { "Concurrent access" }
+ val delegate = Connection(conn, transform)
+ _conn = delegate
+ source.onStart(delegate, now)
+ }
+
+ override fun onStop(conn: FlowConnection, now: Long, delta: Long) {
+ val delegate = checkNotNull(_conn) { "Invariant violation" }
+ _conn = null
+ source.onStop(delegate, now, delta)
+ }
+
+ override fun onPull(conn: FlowConnection, now: Long, delta: Long): Long {
+ val delegate = checkNotNull(_conn) { "Invariant violation" }
+ return source.onPull(delegate, now, delta)
+ }
+
+ override fun onConverge(conn: FlowConnection, now: Long, delta: Long) {
+ val delegate = _conn ?: return
+ source.onConverge(delegate, now, delta)
+ }
+
+ /**
+ * The wrapper [FlowConnection] that is used to transform the flow.
+ */
+ private class Connection(
+ private val delegate: FlowConnection,
+ private val transform: (FlowConnection, Double) -> Double
+ ) : FlowConnection by delegate {
+ override fun push(rate: Double) {
+ delegate.push(transform(this, rate))
+ }
+ }
+}
diff --git a/opendc-simulator/opendc-simulator-flow/src/main/kotlin/org/opendc/simulator/flow/FlowSink.kt b/opendc-simulator/opendc-simulator-flow/src/main/kotlin/org/opendc/simulator/flow/FlowSink.kt
new file mode 100644
index 00000000..b4eb6a38
--- /dev/null
+++ b/opendc-simulator/opendc-simulator-flow/src/main/kotlin/org/opendc/simulator/flow/FlowSink.kt
@@ -0,0 +1,70 @@
+/*
+ * Copyright (c) 2021 AtLarge Research
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
+ * of this software and associated documentation files (the "Software"), to deal
+ * in the Software without restriction, including without limitation the rights
+ * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+ * copies of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included in all
+ * copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
+ * SOFTWARE.
+ */
+
+package org.opendc.simulator.flow
+
+/**
+ * A [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
+) : AbstractFlowConsumer(engine, initialCapacity) {
+
+ override fun start(ctx: FlowConsumerContext) {
+ if (parent != null) {
+ ctx.shouldConsumerConverge = true
+ }
+ super.start(ctx)
+ }
+
+ override fun createLogic(): FlowConsumerLogic {
+ return object : FlowConsumerLogic {
+ private val parent = this@FlowSink.parent
+
+ override fun onPush(
+ ctx: FlowConsumerContext,
+ now: Long,
+ delta: Long,
+ rate: Double
+ ) {
+ updateCounters(ctx, delta)
+ }
+
+ override fun onFinish(ctx: FlowConsumerContext, now: Long, delta: Long, cause: Throwable?) {
+ updateCounters(ctx, delta)
+ cancel()
+ }
+
+ override fun onConverge(ctx: FlowConsumerContext, now: Long, delta: Long) {
+ parent?.onConverge(now, delta)
+ }
+ }
+ }
+
+ override fun toString(): String = "FlowSink[capacity=$capacity]"
+}
diff --git a/opendc-simulator/opendc-simulator-flow/src/main/kotlin/org/opendc/simulator/flow/FlowSource.kt b/opendc-simulator/opendc-simulator-flow/src/main/kotlin/org/opendc/simulator/flow/FlowSource.kt
new file mode 100644
index 00000000..3a7e52aa
--- /dev/null
+++ b/opendc-simulator/opendc-simulator-flow/src/main/kotlin/org/opendc/simulator/flow/FlowSource.kt
@@ -0,0 +1,70 @@
+/*
+ * Copyright (c) 2021 AtLarge Research
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
+ * of this software and associated documentation files (the "Software"), to deal
+ * in the Software without restriction, including without limitation the rights
+ * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+ * copies of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included in all
+ * copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
+ * SOFTWARE.
+ */
+
+package org.opendc.simulator.flow
+
+/**
+ * A source of flow that is consumed by a [FlowConsumer].
+ *
+ * Implementations of this interface should be considered stateful and must be assumed not to be re-usable
+ * (concurrently) for multiple [FlowConsumer]s, unless explicitly said otherwise.
+ */
+public interface FlowSource {
+ /**
+ * This method is invoked when the source is started.
+ *
+ * @param conn The connection between the source and consumer.
+ * @param now The virtual timestamp in milliseconds at which the provider finished.
+ */
+ public fun onStart(conn: FlowConnection, now: Long) {}
+
+ /**
+ * This method is invoked when the source is finished.
+ *
+ * @param conn The connection between the source and consumer.
+ * @param now The virtual timestamp in milliseconds at which the source finished.
+ * @param delta The virtual duration between this call and the last call to [onPull] in milliseconds.
+ */
+ public fun onStop(conn: FlowConnection, now: Long, delta: Long) {}
+
+ /**
+ * This method is invoked when the source is pulled.
+ *
+ * @param conn The connection between the source and consumer.
+ * @param now The virtual timestamp in milliseconds at which the pull is occurring.
+ * @param delta The virtual duration between this call and the last call to [onPull] in milliseconds.
+ * @return The duration after which the resource consumer should be pulled again.
+ */
+ public fun onPull(conn: FlowConnection, now: Long, delta: Long): Long
+
+ /**
+ * This method is invoked when the flow graph has converged into a steady-state system.
+ *
+ * Make sure to enable [FlowConnection.shouldSourceConverge] if you need this callback. By default, this method
+ * will not be invoked.
+ *
+ * @param conn The connection between the source and consumer.
+ * @param now The virtual timestamp in milliseconds at which the system converged.
+ * @param delta The virtual duration between this call and the last call to [onConverge] in milliseconds.
+ */
+ public fun onConverge(conn: FlowConnection, now: Long, delta: Long) {}
+}
diff --git a/opendc-simulator/opendc-simulator-resources/src/main/kotlin/org/opendc/simulator/resources/interference/InterferenceDomain.kt b/opendc-simulator/opendc-simulator-flow/src/main/kotlin/org/opendc/simulator/flow/interference/InterferenceDomain.kt
index 1066777f..aa2713b6 100644
--- a/opendc-simulator/opendc-simulator-resources/src/main/kotlin/org/opendc/simulator/resources/interference/InterferenceDomain.kt
+++ b/opendc-simulator/opendc-simulator-flow/src/main/kotlin/org/opendc/simulator/flow/interference/InterferenceDomain.kt
@@ -1,10 +1,10 @@
-package org.opendc.simulator.resources.interference
+package org.opendc.simulator.flow.interference
-import org.opendc.simulator.resources.SimResourceConsumer
+import org.opendc.simulator.flow.FlowSource
/**
- * An interference domain represents a system of resources where [resource consumers][SimResourceConsumer] may incur
- * performance variability due to operating on the same resources and therefore causing interference.
+ * An interference domain represents a system of flow stages where [flow sources][FlowSource] may incur
+ * performance variability due to operating on the same resource and therefore causing interference.
*/
public interface InterferenceDomain {
/**
diff --git a/opendc-simulator/opendc-simulator-resources/src/main/kotlin/org/opendc/simulator/resources/interference/InterferenceKey.kt b/opendc-simulator/opendc-simulator-flow/src/main/kotlin/org/opendc/simulator/flow/interference/InterferenceKey.kt
index 8b12e7b4..d28ebde5 100644
--- a/opendc-simulator/opendc-simulator-resources/src/main/kotlin/org/opendc/simulator/resources/interference/InterferenceKey.kt
+++ b/opendc-simulator/opendc-simulator-flow/src/main/kotlin/org/opendc/simulator/flow/interference/InterferenceKey.kt
@@ -20,7 +20,7 @@
* SOFTWARE.
*/
-package org.opendc.simulator.resources.interference
+package org.opendc.simulator.flow.interference
/**
* A key that uniquely identifies a participant of an interference domain.
diff --git a/opendc-simulator/opendc-simulator-flow/src/main/kotlin/org/opendc/simulator/flow/internal/Flags.kt b/opendc-simulator/opendc-simulator-flow/src/main/kotlin/org/opendc/simulator/flow/internal/Flags.kt
new file mode 100644
index 00000000..81ed9f26
--- /dev/null
+++ b/opendc-simulator/opendc-simulator-flow/src/main/kotlin/org/opendc/simulator/flow/internal/Flags.kt
@@ -0,0 +1,43 @@
+/*
+ * Copyright (c) 2021 AtLarge Research
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
+ * of this software and associated documentation files (the "Software"), to deal
+ * in the Software without restriction, including without limitation the rights
+ * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+ * copies of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included in all
+ * copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
+ * SOFTWARE.
+ */
+
+package org.opendc.simulator.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 ConnUpdateActive = 1 shl 4 // An update for the connection is active
+internal const val ConnUpdatePending = 1 shl 5 // An (immediate) update of the connection is pending
+internal const val ConnUpdateSkipped = 1 shl 6 // An update of the connection was not necessary
+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
diff --git a/opendc-simulator/opendc-simulator-flow/src/main/kotlin/org/opendc/simulator/flow/internal/FlowConsumerContextImpl.kt b/opendc-simulator/opendc-simulator-flow/src/main/kotlin/org/opendc/simulator/flow/internal/FlowConsumerContextImpl.kt
new file mode 100644
index 00000000..9d36483e
--- /dev/null
+++ b/opendc-simulator/opendc-simulator-flow/src/main/kotlin/org/opendc/simulator/flow/internal/FlowConsumerContextImpl.kt
@@ -0,0 +1,419 @@
+/*
+ * Copyright (c) 2021 AtLarge Research
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
+ * of this software and associated documentation files (the "Software"), to deal
+ * in the Software without restriction, including without limitation the rights
+ * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+ * copies of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included in all
+ * copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
+ * SOFTWARE.
+ */
+
+package org.opendc.simulator.flow.internal
+
+import mu.KotlinLogging
+import org.opendc.simulator.flow.*
+import java.util.*
+import kotlin.math.max
+import kotlin.math.min
+
+/**
+ * Implementation of a [FlowConnection] managing the communication between flow sources and consumers.
+ */
+internal class FlowConsumerContextImpl(
+ private val engine: FlowEngineImpl,
+ private val source: FlowSource,
+ private val logic: FlowConsumerLogic
+) : FlowConsumerContext {
+ /**
+ * The logging instance of this connection.
+ */
+ private val logger = KotlinLogging.logger {}
+
+ /**
+ * The capacity of the connection.
+ */
+ override var capacity: Double
+ get() = _capacity
+ set(value) {
+ val oldValue = _capacity
+
+ // Only changes will be propagated
+ if (value != oldValue) {
+ _capacity = value
+ pull()
+ }
+ }
+ private var _capacity: Double = 0.0
+
+ /**
+ * The current processing rate of the connection.
+ */
+ override val rate: Double
+ get() = _rate
+ private var _rate = 0.0
+
+ /**
+ * The current flow processing demand.
+ */
+ override val demand: Double
+ get() = _demand
+
+ /**
+ * Flags to control the convergence of the consumer and source.
+ */
+ override var shouldSourceConverge: Boolean = false
+ set(value) {
+ field = value
+ _flags =
+ if (value)
+ _flags or ConnConvergeSource
+ else
+ _flags and ConnConvergeSource.inv()
+ }
+ override var shouldConsumerConverge: Boolean = false
+ set(value) {
+ field = value
+
+ _flags =
+ if (value)
+ _flags or ConnConvergeConsumer
+ else
+ _flags and ConnConvergeConsumer.inv()
+ }
+
+ /**
+ * The clock to track simulation time.
+ */
+ private val _clock = engine.clock
+
+ /**
+ * The current state of the connection.
+ */
+ private var _demand: Double = 0.0 // The current (pending) demand of the source
+ private var _deadline: Long = Long.MAX_VALUE // The deadline of the source's timer
+
+ /**
+ * The flags of the flow connection, indicating certain actions.
+ */
+ private var _flags: Int = 0
+
+ /**
+ * The timestamp of calls to the callbacks.
+ */
+ private var _lastPull: Long = Long.MIN_VALUE // Last call to `onPull`
+ private var _lastPush: Long = Long.MIN_VALUE // Last call to `onPush`
+ private var _lastSourceConvergence: Long = Long.MAX_VALUE // Last call to source `onConvergence`
+ private var _lastConsumerConvergence: Long = Long.MAX_VALUE // Last call to consumer `onConvergence`
+
+ /**
+ * The timers at which the context is scheduled to be interrupted.
+ */
+ private var _timer: FlowEngineImpl.Timer? = null
+ private val _pendingTimers: ArrayDeque<FlowEngineImpl.Timer> = 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() {
+ var flags = _flags
+ if (flags and ConnState == ConnClosed) {
+ return
+ }
+
+ engine.batch {
+ // Mark the connection as closed and pulled (in order to converge)
+ flags = (flags and ConnState.inv()) or ConnClosed or ConnPulled
+ _flags = flags
+
+ if (flags and ConnUpdateActive == 0) {
+ val now = _clock.millis()
+ doStopSource(now)
+
+ // FIX: Make sure the context converges
+ scheduleImmediate(now, flags)
+ }
+ }
+ }
+
+ override fun pull() {
+ val flags = _flags
+ if (flags and ConnState != ConnActive) {
+ return
+ }
+
+ // Mark connection as pulled
+ scheduleImmediate(_clock.millis(), flags or ConnPulled)
+ }
+
+ override fun flush() {
+ 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
+ }
+
+ engine.scheduleSync(_clock.millis(), 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: ArrayDeque<FlowConsumerContextImpl>,
+ timerQueue: PriorityQueue<FlowEngineImpl.Timer>,
+ 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 = deadline
+ var hasUpdated = false
+
+ try {
+ // Pull the source if (1) `pull` is called or (2) the timer of the source has expired
+ newDeadline = if (flags and ConnPulled != 0 || reachedDeadline) {
+ val lastPull = _lastPull
+ val delta = max(0, now - lastPull)
+
+ // Update state before calling into the outside world, so it observes a consistent state
+ _lastPull = now
+ _flags = (flags and ConnPulled.inv()) or ConnUpdateActive
+ hasUpdated = true
+
+ val duration = source.onPull(this, now, delta)
+
+ // IMPORTANT: Re-fetch the flags after the callback might have changed those
+ flags = _flags
+
+ if (duration != Long.MAX_VALUE)
+ now + duration
+ else
+ duration
+ } else {
+ deadline
+ }
+
+ // Push to the consumer if the rate of the source has changed (after a call to `push`)
+ val newState = flags and ConnState
+ if (newState == ConnActive && flags and ConnPushed != 0) {
+ val lastPush = _lastPush
+ val delta = max(0, now - lastPush)
+
+ // Update state before calling into the outside world, so it observes a consistent state
+ _lastPush = now
+ _flags = (flags and ConnPushed.inv()) or ConnUpdateActive
+ hasUpdated = true
+
+ logic.onPush(this, now, delta, _demand)
+
+ // IMPORTANT: Re-fetch the flags after the callback might have changed those
+ flags = _flags
+ } else if (newState == ConnClosed) {
+ hasUpdated = true
+
+ // The source has called [FlowConnection.close], so clean up the connection
+ doStopSource(now)
+ }
+ } catch (cause: Throwable) {
+ // Mark the connection as closed
+ flags = (flags and ConnState.inv()) or ConnClosed
+
+ doFailSource(now, cause)
+ }
+
+ // 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
+ // See also `scheduleDelayed`
+ val timer = pendingTimers.poll()
+ _timer = timer
+ timer
+ } else {
+ _timer
+ }
+
+ // Set the new deadline and schedule a delayed update for that deadline
+ _deadline = newDeadline
+
+ // 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) The current active timer for the connection points to a later deadline
+ if (newDeadline == Long.MAX_VALUE || flags and ConnState != ConnActive || (timer != null && newDeadline >= timer.target)) {
+ // 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
+ val newTimer = FlowEngineImpl.Timer(this, newDeadline)
+ _timer = newTimer
+ timerQueue.add(newTimer)
+
+ // A timer already exists for this connection, so add it to the queue of pending timers
+ if (timer != null) {
+ 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 and the connection is active
+ if (flags and ConnState == ConnActive && flags and ConnConvergeSource != 0) {
+ val delta = max(0, now - _lastSourceConvergence)
+ _lastSourceConvergence = now
+
+ source.onConverge(this, now, delta)
+ }
+
+ // Call the consumer callback if it has enabled convergence
+ if (flags and ConnConvergeConsumer != 0) {
+ val delta = max(0, now - _lastConsumerConvergence)
+ _lastConsumerConvergence = now
+
+ logic.onConverge(this, now, delta)
+ }
+ } catch (cause: Throwable) {
+ doFailSource(now, cause)
+ }
+ }
+
+ override fun toString(): String = "FlowConsumerContextImpl[capacity=$capacity,rate=$_rate]"
+
+ /**
+ * Stop the [FlowSource].
+ */
+ private fun doStopSource(now: Long) {
+ try {
+ source.onStop(this, now, max(0, now - _lastPull))
+ doFinishConsumer(now, null)
+ } catch (cause: Throwable) {
+ doFinishConsumer(now, cause)
+ return
+ } finally {
+ _deadline = Long.MAX_VALUE
+ _demand = 0.0
+ }
+ }
+
+ /**
+ * Fail the [FlowSource].
+ */
+ private fun doFailSource(now: Long, cause: Throwable) {
+ try {
+ source.onStop(this, now, max(0, now - _lastPull))
+ } catch (e: Throwable) {
+ e.addSuppressed(cause)
+ doFinishConsumer(now, e)
+ } finally {
+ _deadline = Long.MAX_VALUE
+ _demand = 0.0
+ }
+ }
+
+ /**
+ * Finish the consumer.
+ */
+ private fun doFinishConsumer(now: Long, cause: Throwable?) {
+ try {
+ logic.onFinish(this, now, max(0, now - _lastPush), cause)
+ } catch (e: Throwable) {
+ e.addSuppressed(cause)
+ logger.error(e) { "Uncaught exception" }
+ }
+ }
+
+ /**
+ * Schedule an immediate update for this connection.
+ */
+ private fun scheduleImmediate(now: Long, flags: Int) {
+ // In case an immediate update is already scheduled, no need to do anything
+ if (flags and ConnUpdatePending != 0) {
+ _flags = flags
+ return
+ }
+
+ // Mark the connection that there is an update pending
+ _flags = flags or ConnUpdatePending
+
+ engine.scheduleImmediate(now, this)
+ }
+}
diff --git a/opendc-simulator/opendc-simulator-resources/src/main/kotlin/org/opendc/simulator/resources/impl/SimResourceCountersImpl.kt b/opendc-simulator/opendc-simulator-flow/src/main/kotlin/org/opendc/simulator/flow/internal/FlowCountersImpl.kt
index 827019c5..d2fa5228 100644
--- a/opendc-simulator/opendc-simulator-resources/src/main/kotlin/org/opendc/simulator/resources/impl/SimResourceCountersImpl.kt
+++ b/opendc-simulator/opendc-simulator-flow/src/main/kotlin/org/opendc/simulator/flow/internal/FlowCountersImpl.kt
@@ -20,23 +20,27 @@
* SOFTWARE.
*/
-package org.opendc.simulator.resources.impl
+package org.opendc.simulator.flow.internal
-import org.opendc.simulator.resources.SimResourceCounters
+import org.opendc.simulator.flow.FlowCounters
/**
- * Mutable implementation of the [SimResourceCounters] interface.
+ * Mutable implementation of the [FlowCounters] interface.
*/
-internal class SimResourceCountersImpl : SimResourceCounters {
+internal class FlowCountersImpl : FlowCounters {
override var demand: Double = 0.0
override var actual: Double = 0.0
- override var overcommit: Double = 0.0
+ override var remaining: Double = 0.0
+ override var interference: Double = 0.0
override fun reset() {
demand = 0.0
actual = 0.0
- overcommit = 0.0
+ remaining = 0.0
+ interference = 0.0
}
- override fun toString(): String = "SimResourceCounters[demand=$demand,actual=$actual,overcommit=$overcommit]"
+ override fun toString(): String {
+ return "FlowCounters[demand=$demand,actual=$actual,remaining=$remaining,interference=$interference]"
+ }
}
diff --git a/opendc-simulator/opendc-simulator-flow/src/main/kotlin/org/opendc/simulator/flow/internal/FlowEngineImpl.kt b/opendc-simulator/opendc-simulator-flow/src/main/kotlin/org/opendc/simulator/flow/internal/FlowEngineImpl.kt
new file mode 100644
index 00000000..019b5f10
--- /dev/null
+++ b/opendc-simulator/opendc-simulator-flow/src/main/kotlin/org/opendc/simulator/flow/internal/FlowEngineImpl.kt
@@ -0,0 +1,247 @@
+/*
+ * Copyright (c) 2021 AtLarge Research
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
+ * of this software and associated documentation files (the "Software"), to deal
+ * in the Software without restriction, including without limitation the rights
+ * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+ * copies of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included in all
+ * copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
+ * SOFTWARE.
+ */
+
+package org.opendc.simulator.flow.internal
+
+import kotlinx.coroutines.Delay
+import kotlinx.coroutines.DisposableHandle
+import kotlinx.coroutines.InternalCoroutinesApi
+import kotlinx.coroutines.Runnable
+import org.opendc.simulator.flow.*
+import java.time.Clock
+import java.util.*
+import kotlin.coroutines.ContinuationInterceptor
+import kotlin.coroutines.CoroutineContext
+
+/**
+ * Internal implementation of the [FlowEngine] interface.
+ *
+ * @param context The coroutine context to use.
+ * @param clock The virtual simulation clock.
+ */
+internal class FlowEngineImpl(private val context: CoroutineContext, override val clock: Clock) : FlowEngine {
+ /**
+ * 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 = ArrayDeque<FlowConsumerContextImpl>()
+
+ /**
+ * A priority queue containing the connection updates to be scheduled in the future.
+ */
+ private val futureQueue = PriorityQueue<Timer>()
+
+ /**
+ * 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: ArrayDeque<FlowConsumerContextImpl> = ArrayDeque()
+
+ /**
+ * The index in the batch stack.
+ */
+ private var batchIndex = 0
+
+ /**
+ * 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
+ }
+
+ runEngine(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
+ }
+
+ runEngine(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--
+ }
+ }
+
+ /**
+ * Run the engine and mark as active while running.
+ */
+ private fun runEngine(now: Long) {
+ try {
+ batchIndex++
+ doRunEngine(now)
+ } finally {
+ batchIndex--
+ }
+ }
+
+ /**
+ * 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
+
+ // Remove any entries in the `futureInvocations` queue from the past
+ while (true) {
+ val head = futureInvocations.peek()
+ if (head == null || head.timestamp > now) {
+ break
+ }
+ futureInvocations.poll()
+ }
+
+ // Execute all scheduled updates at current timestamp
+ while (true) {
+ val timer = futureQueue.peek() ?: break
+ val target = timer.target
+
+ if (target > now) {
+ break
+ }
+
+ assert(target >= now) { "Internal inconsistency: found update of the past" }
+
+ futureQueue.poll()
+ timer.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())
+
+ // Schedule an engine invocation for the next update to occur.
+ val headTimer = futureQueue.peek()
+ if (headTimer != null) {
+ trySchedule(now, futureInvocations, headTimer.target)
+ }
+ }
+
+ /**
+ * 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) {
+ while (true) {
+ val invocation = scheduled.peekFirst()
+ if (invocation == null || invocation.timestamp > target) {
+ // Case 2: A new timer was registered ahead of the other timers.
+ // Solution: Schedule a new scheduler invocation
+ @OptIn(InternalCoroutinesApi::class)
+ val handle = delay.invokeOnTimeout(target - now, { runEngine(target) }, context)
+ scheduled.addFirst(Invocation(target, handle))
+ break
+ } else if (invocation.timestamp < target) {
+ // Case 2: A timer was cancelled and the head of the timer queue is now later than excepted
+ // Solution: Cancel the next scheduler invocation
+ scheduled.pollFirst()
+
+ invocation.cancel()
+ } else {
+ break
+ }
+ }
+ }
+
+ /**
+ * 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()
+ }
+
+ /**
+ * An update call for [ctx] that is scheduled for [target].
+ *
+ * This class represents an update in the future at [target] requested by [ctx].
+ */
+ class Timer(@JvmField val ctx: FlowConsumerContextImpl, @JvmField val target: Long) : Comparable<Timer> {
+ override fun compareTo(other: Timer): Int {
+ return target.compareTo(other.target)
+ }
+
+ override fun toString(): String = "Timer[ctx=$ctx,timestamp=$target]"
+ }
+}
diff --git a/opendc-simulator/opendc-simulator-flow/src/main/kotlin/org/opendc/simulator/flow/mux/FlowMultiplexer.kt b/opendc-simulator/opendc-simulator-flow/src/main/kotlin/org/opendc/simulator/flow/mux/FlowMultiplexer.kt
new file mode 100644
index 00000000..04ba7f21
--- /dev/null
+++ b/opendc-simulator/opendc-simulator-flow/src/main/kotlin/org/opendc/simulator/flow/mux/FlowMultiplexer.kt
@@ -0,0 +1,100 @@
+/*
+ * Copyright (c) 2021 AtLarge Research
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
+ * of this software and associated documentation files (the "Software"), to deal
+ * in the Software without restriction, including without limitation the rights
+ * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+ * copies of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included in all
+ * copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
+ * SOFTWARE.
+ */
+
+package org.opendc.simulator.flow.mux
+
+import org.opendc.simulator.flow.FlowConsumer
+import org.opendc.simulator.flow.FlowCounters
+import org.opendc.simulator.flow.FlowSource
+import org.opendc.simulator.flow.interference.InterferenceKey
+
+/**
+ * A [FlowMultiplexer] enables multiplexing multiple [FlowSource]s over possibly multiple [FlowConsumer]s.
+ */
+public interface FlowMultiplexer {
+ /**
+ * The inputs of the multiplexer that can be used to consume sources.
+ */
+ public val inputs: Set<FlowConsumer>
+
+ /**
+ * The outputs of the multiplexer over which the flows will be distributed.
+ */
+ public val outputs: Set<FlowSource>
+
+ /**
+ * The actual processing rate of the multiplexer.
+ */
+ public val rate: Double
+
+ /**
+ * The demanded processing rate of the input.
+ */
+ public val demand: Double
+
+ /**
+ * The capacity of the outputs.
+ */
+ public val capacity: Double
+
+ /**
+ * The flow counters to track the flow metrics of all multiplexer inputs.
+ */
+ public val counters: FlowCounters
+
+ /**
+ * Create a new input on this multiplexer.
+ *
+ * @param key The key of the interference member to which the input belongs.
+ */
+ public fun newInput(key: InterferenceKey? = null): FlowConsumer
+
+ /**
+ * Remove [input] from this multiplexer.
+ */
+ public fun removeInput(input: FlowConsumer)
+
+ /**
+ * Create a new output on this multiplexer.
+ */
+ public fun newOutput(): FlowSource
+
+ /**
+ * Remove [output] from this multiplexer.
+ */
+ public fun removeOutput(output: FlowSource)
+
+ /**
+ * Clear all inputs and outputs from the multiplexer.
+ */
+ public fun clear()
+
+ /**
+ * Clear the inputs of the multiplexer.
+ */
+ public fun clearInputs()
+
+ /**
+ * Clear the outputs of the multiplexer.
+ */
+ public fun clearOutputs()
+}
diff --git a/opendc-simulator/opendc-simulator-flow/src/main/kotlin/org/opendc/simulator/flow/mux/ForwardingFlowMultiplexer.kt b/opendc-simulator/opendc-simulator-flow/src/main/kotlin/org/opendc/simulator/flow/mux/ForwardingFlowMultiplexer.kt
new file mode 100644
index 00000000..125d10fe
--- /dev/null
+++ b/opendc-simulator/opendc-simulator-flow/src/main/kotlin/org/opendc/simulator/flow/mux/ForwardingFlowMultiplexer.kt
@@ -0,0 +1,154 @@
+/*
+ * Copyright (c) 2021 AtLarge Research
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
+ * of this software and associated documentation files (the "Software"), to deal
+ * in the Software without restriction, including without limitation the rights
+ * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+ * copies of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included in all
+ * copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
+ * SOFTWARE.
+ */
+
+package org.opendc.simulator.flow.mux
+
+import org.opendc.simulator.flow.*
+import org.opendc.simulator.flow.interference.InterferenceKey
+import java.util.ArrayDeque
+
+/**
+ * A [FlowMultiplexer] implementation that allocates inputs to the outputs of the multiplexer exclusively. This means
+ * that a single input is directly connected to an output and that the multiplexer can only support as many
+ * inputs as outputs.
+ *
+ * @param engine The [FlowEngine] driving the simulation.
+ */
+public class ForwardingFlowMultiplexer(private val engine: FlowEngine) : FlowMultiplexer {
+ override val inputs: Set<FlowConsumer>
+ get() = _inputs
+ private val _inputs = mutableSetOf<Input>()
+
+ override val outputs: Set<FlowSource>
+ get() = _outputs
+ private val _outputs = mutableSetOf<Output>()
+ private val _availableOutputs = ArrayDeque<Output>()
+
+ override val counters: FlowCounters = object : FlowCounters {
+ override val demand: Double
+ get() = _outputs.sumOf { it.forwarder.counters.demand }
+ override val actual: Double
+ get() = _outputs.sumOf { it.forwarder.counters.actual }
+ override val remaining: Double
+ get() = _outputs.sumOf { it.forwarder.counters.remaining }
+ override val interference: Double
+ get() = _outputs.sumOf { it.forwarder.counters.interference }
+
+ override fun reset() {
+ for (output in _outputs) {
+ output.forwarder.counters.reset()
+ }
+ }
+
+ override fun toString(): String = "FlowCounters[demand=$demand,actual=$actual,remaining=$remaining]"
+ }
+
+ override val rate: Double
+ get() = _outputs.sumOf { it.forwarder.rate }
+
+ override val demand: Double
+ get() = _outputs.sumOf { it.forwarder.demand }
+
+ override val capacity: Double
+ get() = _outputs.sumOf { it.forwarder.capacity }
+
+ override fun newInput(key: InterferenceKey?): FlowConsumer {
+ val output = checkNotNull(_availableOutputs.poll()) { "No capacity to serve request" }
+ val input = Input(output)
+ _inputs += input
+ return input
+ }
+
+ override fun removeInput(input: FlowConsumer) {
+ if (!_inputs.remove(input)) {
+ return
+ }
+
+ val output = (input as Input).output
+ output.forwarder.cancel()
+ _availableOutputs += output
+ }
+
+ override fun newOutput(): FlowSource {
+ val forwarder = FlowForwarder(engine)
+ val output = Output(forwarder)
+
+ _outputs += output
+ return output
+ }
+
+ override fun removeOutput(output: FlowSource) {
+ if (!_outputs.remove(output)) {
+ return
+ }
+
+ val forwarder = (output as Output).forwarder
+ forwarder.close()
+ }
+
+ override fun clearInputs() {
+ for (input in _inputs) {
+ val output = input.output
+ output.forwarder.cancel()
+ _availableOutputs += output
+ }
+
+ _inputs.clear()
+ }
+
+ override fun clearOutputs() {
+ for (output in _outputs) {
+ output.forwarder.cancel()
+ }
+ _outputs.clear()
+ _availableOutputs.clear()
+ }
+
+ override fun clear() {
+ clearOutputs()
+ clearInputs()
+ }
+
+ /**
+ * An input on the multiplexer.
+ */
+ private inner class Input(@JvmField val output: Output) : FlowConsumer by output.forwarder {
+ override fun toString(): String = "ForwardingFlowMultiplexer.Input"
+ }
+
+ /**
+ * An output on the multiplexer.
+ */
+ private inner class Output(@JvmField val forwarder: FlowForwarder) : FlowSource by forwarder {
+ override fun onStart(conn: FlowConnection, now: Long) {
+ _availableOutputs += this
+ forwarder.onStart(conn, now)
+ }
+
+ override fun onStop(conn: FlowConnection, now: Long, delta: Long) {
+ forwarder.cancel()
+ forwarder.onStop(conn, now, delta)
+ }
+
+ override fun toString(): String = "ForwardingFlowMultiplexer.Output"
+ }
+}
diff --git a/opendc-simulator/opendc-simulator-flow/src/main/kotlin/org/opendc/simulator/flow/mux/MaxMinFlowMultiplexer.kt b/opendc-simulator/opendc-simulator-flow/src/main/kotlin/org/opendc/simulator/flow/mux/MaxMinFlowMultiplexer.kt
new file mode 100644
index 00000000..5ff0fb8d
--- /dev/null
+++ b/opendc-simulator/opendc-simulator-flow/src/main/kotlin/org/opendc/simulator/flow/mux/MaxMinFlowMultiplexer.kt
@@ -0,0 +1,490 @@
+/*
+ * Copyright (c) 2021 AtLarge Research
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
+ * of this software and associated documentation files (the "Software"), to deal
+ * in the Software without restriction, including without limitation the rights
+ * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+ * copies of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included in all
+ * copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
+ * SOFTWARE.
+ */
+
+package org.opendc.simulator.flow.mux
+
+import org.opendc.simulator.flow.*
+import org.opendc.simulator.flow.interference.InterferenceDomain
+import org.opendc.simulator.flow.interference.InterferenceKey
+import org.opendc.simulator.flow.internal.FlowCountersImpl
+import kotlin.math.max
+import kotlin.math.min
+
+/**
+ * A [FlowMultiplexer] implementation that multiplexes flows over the available outputs using max-min fair sharing.
+ *
+ * @param engine The [FlowEngine] to drive the flow simulation.
+ * @param parent The parent flow system of the multiplexer.
+ * @param interferenceDomain The interference domain of the multiplexer.
+ */
+public class MaxMinFlowMultiplexer(
+ private val engine: FlowEngine,
+ private val parent: FlowConvergenceListener? = null,
+ private val interferenceDomain: InterferenceDomain? = null
+) : FlowMultiplexer {
+ /**
+ * The inputs of the multiplexer.
+ */
+ override val inputs: Set<FlowConsumer>
+ get() = _inputs
+ private val _inputs = mutableSetOf<Input>()
+ private val _activeInputs = mutableListOf<Input>()
+
+ /**
+ * The outputs of the multiplexer.
+ */
+ override val outputs: Set<FlowSource>
+ get() = _outputs
+ private val _outputs = mutableSetOf<Output>()
+ private val _activeOutputs = mutableListOf<Output>()
+
+ /**
+ * The flow counters of this multiplexer.
+ */
+ public override val counters: FlowCounters
+ get() = _counters
+ private val _counters = FlowCountersImpl()
+
+ /**
+ * The actual processing rate of the multiplexer.
+ */
+ public override val rate: Double
+ get() = _rate
+ private var _rate = 0.0
+
+ /**
+ * The demanded processing rate of the input.
+ */
+ public override val demand: Double
+ get() = _demand
+ private var _demand = 0.0
+
+ /**
+ * The capacity of the outputs.
+ */
+ public override val capacity: Double
+ get() = _capacity
+ private var _capacity = 0.0
+
+ /**
+ * Flag to indicate that the scheduler is active.
+ */
+ private var _schedulerActive = false
+ private var _lastSchedulerCycle = Long.MAX_VALUE
+
+ /**
+ * The last convergence timestamp and the input.
+ */
+ private var _lastConverge: Long = Long.MIN_VALUE
+ private var _lastConvergeInput: Input? = null
+
+ override fun newInput(key: InterferenceKey?): FlowConsumer {
+ val provider = Input(_capacity, key)
+ _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()
+ _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()
+ }
+
+ /**
+ * Converge the scheduler of the multiplexer.
+ */
+ private fun runScheduler(now: Long) {
+ if (_schedulerActive) {
+ return
+ }
+ val lastSchedulerCycle = _lastSchedulerCycle
+ val delta = max(0, now - lastSchedulerCycle)
+ _schedulerActive = true
+ _lastSchedulerCycle = now
+
+ try {
+ doSchedule(now, delta)
+ } finally {
+ _schedulerActive = false
+ }
+ }
+
+ /**
+ * Schedule the inputs over the outputs.
+ */
+ private fun doSchedule(now: Long, delta: Long) {
+ val activeInputs = _activeInputs
+ val activeOutputs = _activeOutputs
+
+ // Update the counters of the scheduler
+ updateCounters(delta)
+
+ // If there is no work yet, mark the inputs as idle.
+ if (activeInputs.isEmpty()) {
+ _demand = 0.0
+ _rate = 0.0
+ return
+ }
+
+ val capacity = _capacity
+ var availableCapacity = capacity
+
+ // Pull in the work of the outputs
+ val inputIterator = activeInputs.listIterator()
+ for (input in inputIterator) {
+ input.pull(now)
+
+ // Remove outputs that have finished
+ if (!input.isActive) {
+ input.actualRate = 0.0
+ inputIterator.remove()
+ }
+ }
+
+ var demand = 0.0
+
+ // Sort in-place the inputs based on their pushed flow.
+ // Profiling shows that it is faster than maintaining some kind of sorted set.
+ activeInputs.sort()
+
+ // Divide the available output capacity fairly over the inputs using max-min fair sharing
+ var remaining = activeInputs.size
+ for (i in activeInputs.indices) {
+ val input = activeInputs[i]
+ val availableShare = availableCapacity / remaining--
+ val grantedRate = min(input.allowedRate, availableShare)
+
+ // Ignore empty sources
+ if (grantedRate <= 0.0) {
+ input.actualRate = 0.0
+ continue
+ }
+
+ input.actualRate = grantedRate
+ demand += input.limit
+ availableCapacity -= grantedRate
+ }
+
+ val rate = capacity - availableCapacity
+
+ _demand = demand
+ _rate = rate
+
+ // Sort all consumers by their capacity
+ activeOutputs.sort()
+
+ // 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)
+ }
+ }
+
+ /**
+ * Recompute the capacity of the multiplexer.
+ */
+ private fun updateCapacity() {
+ val newCapacity = _activeOutputs.sumOf(Output::capacity)
+
+ // No-op if the capacity is unchanged
+ if (_capacity == newCapacity) {
+ return
+ }
+
+ _capacity = newCapacity
+
+ for (input in _inputs) {
+ input.capacity = newCapacity
+ }
+ }
+
+ /**
+ * The previous capacity of the multiplexer.
+ */
+ private var _previousCapacity = 0.0
+
+ /**
+ * Update the counters of the scheduler.
+ */
+ private fun updateCounters(delta: Long) {
+ val previousCapacity = _previousCapacity
+ _previousCapacity = _capacity
+
+ if (delta <= 0) {
+ return
+ }
+
+ val deltaS = delta / 1000.0
+
+ _counters.demand += _demand * deltaS
+ _counters.actual += _rate * deltaS
+ _counters.remaining += (previousCapacity - _rate) * deltaS
+ }
+
+ /**
+ * An internal [FlowConsumer] implementation for multiplexer inputs.
+ */
+ private inner class Input(capacity: Double, val key: InterferenceKey?) :
+ AbstractFlowConsumer(engine, capacity),
+ FlowConsumerLogic,
+ Comparable<Input> {
+ /**
+ * 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.
+ */
+ val allowedRate: Double
+ get() = min(capacity, limit)
+
+ /**
+ * A flag to indicate that the input is closed.
+ */
+ private var _isClosed: Boolean = false
+
+ /**
+ * The timestamp at which we received the last command.
+ */
+ private var _lastPull: Long = Long.MIN_VALUE
+
+ /**
+ * The interference domain this input belongs to.
+ */
+ private val interferenceDomain = this@MaxMinFlowMultiplexer.interferenceDomain
+
+ /**
+ * Close the input.
+ *
+ * This method is invoked when the user removes an input from the switch.
+ */
+ fun close() {
+ _isClosed = true
+ cancel()
+ }
+
+ /* AbstractFlowConsumer */
+ override fun createLogic(): FlowConsumerLogic = this
+
+ override fun start(ctx: FlowConsumerContext) {
+ check(!_isClosed) { "Cannot re-use closed input" }
+
+ _activeInputs += this
+ if (parent != null) {
+ ctx.shouldConsumerConverge = true
+ }
+
+ super.start(ctx)
+ }
+
+ /* FlowConsumerLogic */
+ override fun onPush(
+ ctx: FlowConsumerContext,
+ now: Long,
+ delta: Long,
+ rate: Double
+ ) {
+ doUpdateCounters(delta)
+
+ actualRate = 0.0
+ limit = rate
+ _lastPull = now
+
+ runScheduler(now)
+ }
+
+ override fun onConverge(ctx: FlowConsumerContext, now: Long, delta: Long) {
+ val lastConverge = _lastConverge
+ val parent = parent
+
+ if (parent != null && (lastConverge < now || _lastConvergeInput == null)) {
+ _lastConverge = now
+ _lastConvergeInput = this
+
+ parent.onConverge(now, max(0, now - lastConverge))
+ }
+ }
+
+ override fun onFinish(ctx: FlowConsumerContext, now: Long, delta: Long, cause: Throwable?) {
+ doUpdateCounters(delta)
+
+ limit = 0.0
+ actualRate = 0.0
+ _lastPull = now
+
+ // Assign a new input responsible for handling the convergence events
+ if (_lastConvergeInput == this) {
+ _lastConvergeInput = null
+ }
+
+ // Re-run scheduler to distribute new load
+ runScheduler(now)
+ }
+
+ /* Comparable */
+ override fun compareTo(other: Input): Int = allowedRate.compareTo(other.allowedRate)
+
+ /**
+ * Pull the source if necessary.
+ */
+ fun pull(now: Long) {
+ val ctx = ctx
+ if (ctx != null && _lastPull < now) {
+ ctx.flush()
+ }
+ }
+
+ /**
+ * Helper method to update the flow counters of the multiplexer.
+ */
+ private fun doUpdateCounters(delta: Long) {
+ if (delta <= 0L) {
+ return
+ }
+
+ // Compute the performance penalty due to flow interference
+ val perfScore = if (interferenceDomain != null) {
+ val load = _rate / _capacity
+ interferenceDomain.apply(key, load)
+ } else {
+ 1.0
+ }
+
+ val deltaS = delta / 1000.0
+ val demand = limit * deltaS
+ val actual = actualRate * deltaS
+ val remaining = (capacity - actualRate) * deltaS
+
+ updateCounters(demand, actual, remaining)
+
+ _counters.interference += actual * max(0.0, 1 - perfScore)
+ }
+ }
+
+ /**
+ * An internal [FlowSource] implementation for multiplexer outputs.
+ */
+ private inner class Output : 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
+
+ /**
+ * Push the specified rate to the consumer.
+ */
+ fun push(rate: Double) {
+ _conn?.push(rate)
+ }
+
+ /**
+ * Cancel this output.
+ */
+ fun cancel() {
+ _conn?.close()
+ }
+
+ override fun onStart(conn: FlowConnection, now: Long) {
+ assert(_conn == null) { "Source running concurrently" }
+ _conn = conn
+ capacity = conn.capacity
+ _activeOutputs.add(this)
+
+ updateCapacity()
+ }
+
+ override fun onStop(conn: FlowConnection, now: Long, delta: Long) {
+ _conn = null
+ capacity = 0.0
+ _activeOutputs.remove(this)
+
+ updateCapacity()
+
+ runScheduler(now)
+ }
+
+ override fun onPull(conn: FlowConnection, now: Long, delta: Long): Long {
+ val capacity = capacity
+ if (capacity != conn.capacity) {
+ this.capacity = capacity
+ updateCapacity()
+ }
+
+ // Re-run scheduler to distribute new load
+ runScheduler(now)
+ return Long.MAX_VALUE
+ }
+
+ override fun compareTo(other: Output): Int = capacity.compareTo(other.capacity)
+ }
+}
diff --git a/opendc-simulator/opendc-simulator-resources/src/main/kotlin/org/opendc/simulator/resources/consumer/SimWorkConsumer.kt b/opendc-simulator/opendc-simulator-flow/src/main/kotlin/org/opendc/simulator/flow/source/FixedFlowSource.kt
index faa693c4..d9779c6a 100644
--- a/opendc-simulator/opendc-simulator-resources/src/main/kotlin/org/opendc/simulator/resources/consumer/SimWorkConsumer.kt
+++ b/opendc-simulator/opendc-simulator-flow/src/main/kotlin/org/opendc/simulator/flow/source/FixedFlowSource.kt
@@ -20,39 +20,38 @@
* SOFTWARE.
*/
-package org.opendc.simulator.resources.consumer
+package org.opendc.simulator.flow.source
-import org.opendc.simulator.resources.SimResourceCommand
-import org.opendc.simulator.resources.SimResourceConsumer
-import org.opendc.simulator.resources.SimResourceContext
+import org.opendc.simulator.flow.FlowConnection
+import org.opendc.simulator.flow.FlowSource
+import kotlin.math.roundToLong
/**
- * A [SimResourceConsumer] that consumes the specified amount of work at the specified utilization.
+ * A [FlowSource] that contains a fixed [amount] and is pushed with a given [utilization].
*/
-public class SimWorkConsumer(
- private val work: Double,
- private val utilization: Double
-) : SimResourceConsumer {
+public class FixedFlowSource(private val amount: Double, private val utilization: Double) : FlowSource {
init {
- require(work >= 0.0) { "Work must be positive" }
- require(utilization > 0.0 && utilization <= 1.0) { "Utilization must be in (0, 1]" }
+ require(amount >= 0.0) { "Amount must be positive" }
+ require(utilization > 0.0) { "Utilization must be positive" }
}
- private var isFirst = true
+ private var remainingAmount = amount
- override fun onNext(ctx: SimResourceContext): SimResourceCommand {
- val limit = ctx.capacity * utilization
- val work = if (isFirst) {
- isFirst = false
- work
- } else {
- ctx.remainingWork
- }
- return if (work > 0.0) {
- SimResourceCommand.Consume(work, limit)
+ override fun onPull(conn: FlowConnection, now: Long, delta: Long): Long {
+ val consumed = conn.rate * delta / 1000.0
+ val limit = conn.capacity * utilization
+
+ remainingAmount -= consumed
+
+ val duration = (remainingAmount / limit * 1000).roundToLong()
+
+ return if (duration > 0) {
+ conn.push(limit)
+ duration
} else {
- SimResourceCommand.Exit
+ conn.close()
+ Long.MAX_VALUE
}
}
}
diff --git a/opendc-simulator/opendc-simulator-resources/src/main/kotlin/org/opendc/simulator/resources/consumer/SimConsumerBarrier.kt b/opendc-simulator/opendc-simulator-flow/src/main/kotlin/org/opendc/simulator/flow/source/FlowSourceBarrier.kt
index 52a42241..b3191ad3 100644
--- a/opendc-simulator/opendc-simulator-resources/src/main/kotlin/org/opendc/simulator/resources/consumer/SimConsumerBarrier.kt
+++ b/opendc-simulator/opendc-simulator-flow/src/main/kotlin/org/opendc/simulator/flow/source/FlowSourceBarrier.kt
@@ -20,13 +20,13 @@
* SOFTWARE.
*/
-package org.opendc.simulator.resources.consumer
+package org.opendc.simulator.flow.source
/**
- * The [SimConsumerBarrier] is a barrier that allows consumers to wait for a select number of other consumers to
- * complete, before proceeding its operation.
+ * The [FlowSourceBarrier] is a barrier that allows multiple sources to wait for a select number of other sources to
+ * finish a pull, before proceeding its operation.
*/
-public class SimConsumerBarrier(public val parties: Int) {
+public class FlowSourceBarrier(public val parties: Int) {
private var counter = 0
/**
diff --git a/opendc-simulator/opendc-simulator-flow/src/main/kotlin/org/opendc/simulator/flow/source/FlowSourceRateAdapter.kt b/opendc-simulator/opendc-simulator-flow/src/main/kotlin/org/opendc/simulator/flow/source/FlowSourceRateAdapter.kt
new file mode 100644
index 00000000..6dd60d95
--- /dev/null
+++ b/opendc-simulator/opendc-simulator-flow/src/main/kotlin/org/opendc/simulator/flow/source/FlowSourceRateAdapter.kt
@@ -0,0 +1,77 @@
+/*
+ * Copyright (c) 2021 AtLarge Research
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
+ * of this software and associated documentation files (the "Software"), to deal
+ * in the Software without restriction, including without limitation the rights
+ * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+ * copies of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included in all
+ * copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
+ * SOFTWARE.
+ */
+
+package org.opendc.simulator.flow.source
+
+import org.opendc.simulator.flow.FlowConnection
+import org.opendc.simulator.flow.FlowSource
+
+/**
+ * Helper class to expose an observable [rate] field describing the flow rate of the source.
+ */
+public class FlowSourceRateAdapter(
+ private val delegate: FlowSource,
+ private val callback: (Double) -> Unit = {}
+) : FlowSource by delegate {
+ /**
+ * The resource processing speed at this instant.
+ */
+ public var rate: Double = 0.0
+ private set(value) {
+ if (field != value) {
+ callback(value)
+ field = value
+ }
+ }
+
+ init {
+ callback(0.0)
+ }
+
+ override fun onStart(conn: FlowConnection, now: Long) {
+ conn.shouldSourceConverge = true
+
+ delegate.onStart(conn, now)
+ }
+
+ override fun onStop(conn: FlowConnection, now: Long, delta: Long) {
+ try {
+ delegate.onStop(conn, now, delta)
+ } finally {
+ rate = 0.0
+ }
+ }
+
+ override fun onPull(conn: FlowConnection, now: Long, delta: Long): Long {
+ return delegate.onPull(conn, now, delta)
+ }
+
+ override fun onConverge(conn: FlowConnection, now: Long, delta: Long) {
+ try {
+ delegate.onConverge(conn, now, delta)
+ } finally {
+ rate = conn.rate
+ }
+ }
+
+ override fun toString(): String = "FlowSourceRateAdapter[delegate=$delegate]"
+}
diff --git a/opendc-simulator/opendc-simulator-flow/src/main/kotlin/org/opendc/simulator/flow/source/TraceFlowSource.kt b/opendc-simulator/opendc-simulator-flow/src/main/kotlin/org/opendc/simulator/flow/source/TraceFlowSource.kt
new file mode 100644
index 00000000..ae537845
--- /dev/null
+++ b/opendc-simulator/opendc-simulator-flow/src/main/kotlin/org/opendc/simulator/flow/source/TraceFlowSource.kt
@@ -0,0 +1,67 @@
+/*
+ * Copyright (c) 2021 AtLarge Research
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
+ * of this software and associated documentation files (the "Software"), to deal
+ * in the Software without restriction, including without limitation the rights
+ * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+ * copies of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included in all
+ * copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
+ * SOFTWARE.
+ */
+
+package org.opendc.simulator.flow.source
+
+import org.opendc.simulator.flow.FlowConnection
+import org.opendc.simulator.flow.FlowSource
+
+/**
+ * A [FlowSource] that replays a sequence of [Fragment], each indicating the flow rate for some period of time.
+ */
+public class TraceFlowSource(private val trace: Sequence<Fragment>) : FlowSource {
+ private var _iterator: Iterator<Fragment>? = null
+ private var _nextTarget = Long.MIN_VALUE
+
+ override fun onStart(conn: FlowConnection, now: Long) {
+ check(_iterator == null) { "Source already running" }
+ _iterator = trace.iterator()
+ }
+
+ override fun onStop(conn: FlowConnection, now: Long, delta: Long) {
+ _iterator = null
+ }
+
+ override fun onPull(conn: FlowConnection, now: Long, delta: Long): Long {
+ // Check whether the trace fragment was fully consumed, otherwise wait until we have done so
+ val nextTarget = _nextTarget
+ if (nextTarget > now) {
+ return now - nextTarget
+ }
+
+ val iterator = checkNotNull(_iterator)
+ return if (iterator.hasNext()) {
+ val fragment = iterator.next()
+ _nextTarget = now + fragment.duration
+ conn.push(fragment.usage)
+ fragment.duration
+ } else {
+ conn.close()
+ Long.MAX_VALUE
+ }
+ }
+
+ /**
+ * A fragment of the trace.
+ */
+ public data class Fragment(val duration: Long, val usage: Double)
+}
diff --git a/opendc-simulator/opendc-simulator-flow/src/test/kotlin/org/opendc/simulator/flow/FlowConsumerContextTest.kt b/opendc-simulator/opendc-simulator-flow/src/test/kotlin/org/opendc/simulator/flow/FlowConsumerContextTest.kt
new file mode 100644
index 00000000..fe39eb2c
--- /dev/null
+++ b/opendc-simulator/opendc-simulator-flow/src/test/kotlin/org/opendc/simulator/flow/FlowConsumerContextTest.kt
@@ -0,0 +1,104 @@
+/*
+ * Copyright (c) 2021 AtLarge Research
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
+ * of this software and associated documentation files (the "Software"), to deal
+ * in the Software without restriction, including without limitation the rights
+ * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+ * copies of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included in all
+ * copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
+ * SOFTWARE.
+ */
+
+package org.opendc.simulator.flow
+
+import io.mockk.*
+import org.junit.jupiter.api.*
+import org.opendc.simulator.core.runBlockingSimulation
+import org.opendc.simulator.flow.internal.FlowConsumerContextImpl
+import org.opendc.simulator.flow.internal.FlowEngineImpl
+
+/**
+ * A test suite for the [FlowConsumerContextImpl] class.
+ */
+class FlowConsumerContextTest {
+ @Test
+ fun testFlushWithoutCommand() = runBlockingSimulation {
+ val engine = FlowEngineImpl(coroutineContext, clock)
+ val consumer = object : FlowSource {
+ override fun onPull(conn: FlowConnection, now: Long, delta: Long): Long {
+ return if (now == 0L) {
+ conn.push(1.0)
+ 1000
+ } else {
+ conn.close()
+ Long.MAX_VALUE
+ }
+ }
+ }
+
+ val logic = object : FlowConsumerLogic {}
+ val context = FlowConsumerContextImpl(engine, consumer, logic)
+
+ engine.scheduleSync(engine.clock.millis(), context)
+ }
+
+ @Test
+ fun testDoubleStart() = runBlockingSimulation {
+ val engine = FlowEngineImpl(coroutineContext, clock)
+ val consumer = object : FlowSource {
+ override fun onPull(conn: FlowConnection, now: Long, delta: Long): Long {
+ return if (now == 0L) {
+ conn.push(0.0)
+ 1000
+ } else {
+ conn.close()
+ Long.MAX_VALUE
+ }
+ }
+ }
+
+ val logic = object : FlowConsumerLogic {}
+ val context = FlowConsumerContextImpl(engine, consumer, logic)
+
+ context.start()
+
+ assertThrows<IllegalStateException> {
+ context.start()
+ }
+ }
+
+ @Test
+ fun testIdempotentCapacityChange() = runBlockingSimulation {
+ val engine = FlowEngineImpl(coroutineContext, clock)
+ val consumer = spyk(object : FlowSource {
+ override fun onPull(conn: FlowConnection, now: Long, delta: Long): Long {
+ return if (now == 0L) {
+ conn.push(1.0)
+ 1000
+ } else {
+ conn.close()
+ Long.MAX_VALUE
+ }
+ }
+ })
+
+ val logic = object : FlowConsumerLogic {}
+ val context = FlowConsumerContextImpl(engine, consumer, logic)
+ context.capacity = 4200.0
+ context.start()
+ context.capacity = 4200.0
+
+ verify(exactly = 1) { consumer.onPull(any(), any(), any()) }
+ }
+}
diff --git a/opendc-simulator/opendc-simulator-flow/src/test/kotlin/org/opendc/simulator/flow/FlowForwarderTest.kt b/opendc-simulator/opendc-simulator-flow/src/test/kotlin/org/opendc/simulator/flow/FlowForwarderTest.kt
new file mode 100644
index 00000000..12e72b8f
--- /dev/null
+++ b/opendc-simulator/opendc-simulator-flow/src/test/kotlin/org/opendc/simulator/flow/FlowForwarderTest.kt
@@ -0,0 +1,321 @@
+/*
+ * Copyright (c) 2021 AtLarge Research
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
+ * of this software and associated documentation files (the "Software"), to deal
+ * in the Software without restriction, including without limitation the rights
+ * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+ * copies of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included in all
+ * copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
+ * SOFTWARE.
+ */
+
+package org.opendc.simulator.flow
+
+import io.mockk.*
+import kotlinx.coroutines.*
+import org.junit.jupiter.api.Assertions.*
+import org.junit.jupiter.api.Disabled
+import org.junit.jupiter.api.Test
+import org.junit.jupiter.api.assertThrows
+import org.opendc.simulator.core.runBlockingSimulation
+import org.opendc.simulator.flow.internal.FlowEngineImpl
+import org.opendc.simulator.flow.source.FixedFlowSource
+
+/**
+ * A test suite for the [FlowForwarder] class.
+ */
+internal class FlowForwarderTest {
+ @Test
+ fun testCancelImmediately() = runBlockingSimulation {
+ val engine = FlowEngineImpl(coroutineContext, clock)
+ val forwarder = FlowForwarder(engine)
+ val source = FlowSink(engine, 2000.0)
+
+ launch { source.consume(forwarder) }
+
+ forwarder.consume(object : FlowSource {
+ override fun onPull(conn: FlowConnection, now: Long, delta: Long): Long {
+ conn.close()
+ return Long.MAX_VALUE
+ }
+ })
+
+ forwarder.close()
+ source.cancel()
+ }
+
+ @Test
+ fun testCancel() = runBlockingSimulation {
+ val engine = FlowEngineImpl(coroutineContext, clock)
+ val forwarder = FlowForwarder(engine)
+ val source = FlowSink(engine, 2000.0)
+
+ launch { source.consume(forwarder) }
+
+ forwarder.consume(object : FlowSource {
+ var isFirst = true
+
+ override fun onPull(conn: FlowConnection, now: Long, delta: Long): Long {
+ return if (isFirst) {
+ isFirst = false
+ conn.push(1.0)
+ 10 * 1000
+ } else {
+ conn.close()
+ Long.MAX_VALUE
+ }
+ }
+ })
+
+ forwarder.close()
+ source.cancel()
+ }
+
+ @Test
+ fun testState() = runBlockingSimulation {
+ val engine = FlowEngineImpl(coroutineContext, clock)
+ val forwarder = FlowForwarder(engine)
+ val consumer = object : FlowSource {
+ override fun onPull(conn: FlowConnection, now: Long, delta: Long): Long {
+ conn.close()
+ return Long.MAX_VALUE
+ }
+ }
+
+ assertFalse(forwarder.isActive)
+
+ forwarder.startConsumer(consumer)
+ assertTrue(forwarder.isActive)
+
+ assertThrows<IllegalStateException> { forwarder.startConsumer(consumer) }
+
+ forwarder.cancel()
+ assertFalse(forwarder.isActive)
+
+ forwarder.close()
+ assertFalse(forwarder.isActive)
+ }
+
+ @Test
+ fun testCancelPendingDelegate() = runBlockingSimulation {
+ val engine = FlowEngineImpl(coroutineContext, clock)
+ val forwarder = FlowForwarder(engine)
+
+ val consumer = spyk(object : FlowSource {
+ override fun onPull(conn: FlowConnection, now: Long, delta: Long): Long {
+ conn.close()
+ return Long.MAX_VALUE
+ }
+ })
+
+ forwarder.startConsumer(consumer)
+ forwarder.cancel()
+
+ verify(exactly = 0) { consumer.onStop(any(), any(), any()) }
+ }
+
+ @Test
+ fun testCancelStartedDelegate() = runBlockingSimulation {
+ val engine = FlowEngineImpl(coroutineContext, clock)
+ val forwarder = FlowForwarder(engine)
+ val source = FlowSink(engine, 2000.0)
+
+ val consumer = spyk(FixedFlowSource(2000.0, 1.0))
+
+ source.startConsumer(forwarder)
+ yield()
+ forwarder.startConsumer(consumer)
+ yield()
+ forwarder.cancel()
+
+ verify(exactly = 1) { consumer.onStart(any(), any()) }
+ verify(exactly = 1) { consumer.onStop(any(), any(), any()) }
+ }
+
+ @Test
+ fun testCancelPropagation() = runBlockingSimulation {
+ val engine = FlowEngineImpl(coroutineContext, clock)
+ val forwarder = FlowForwarder(engine)
+ val source = FlowSink(engine, 2000.0)
+
+ val consumer = spyk(FixedFlowSource(2000.0, 1.0))
+
+ source.startConsumer(forwarder)
+ yield()
+ forwarder.startConsumer(consumer)
+ yield()
+ source.cancel()
+
+ verify(exactly = 1) { consumer.onStart(any(), any()) }
+ verify(exactly = 1) { consumer.onStop(any(), any(), any()) }
+ }
+
+ @Test
+ fun testExitPropagation() = runBlockingSimulation {
+ val engine = FlowEngineImpl(coroutineContext, clock)
+ val forwarder = FlowForwarder(engine, isCoupled = true)
+ val source = FlowSink(engine, 2000.0)
+
+ val consumer = object : FlowSource {
+ override fun onPull(conn: FlowConnection, now: Long, delta: Long): Long {
+ conn.close()
+ return Long.MAX_VALUE
+ }
+ }
+
+ source.startConsumer(forwarder)
+ forwarder.consume(consumer)
+ yield()
+
+ assertFalse(forwarder.isActive)
+ }
+
+ @Test
+ @Disabled // Due to Kotlin bug: https://github.com/mockk/mockk/issues/368
+ fun testAdjustCapacity() = runBlockingSimulation {
+ val engine = FlowEngineImpl(coroutineContext, clock)
+ val forwarder = FlowForwarder(engine)
+ val sink = FlowSink(engine, 1.0)
+
+ val source = spyk(FixedFlowSource(2.0, 1.0))
+ sink.startConsumer(forwarder)
+
+ coroutineScope {
+ launch { forwarder.consume(source) }
+ delay(1000)
+ sink.capacity = 0.5
+ }
+
+ assertEquals(3000, clock.millis())
+ verify(exactly = 1) { source.onPull(any(), any(), any()) }
+ }
+
+ @Test
+ fun testCounters() = runBlockingSimulation {
+ val engine = FlowEngineImpl(coroutineContext, clock)
+ val forwarder = FlowForwarder(engine)
+ val source = FlowSink(engine, 1.0)
+
+ val consumer = FixedFlowSource(2.0, 1.0)
+ source.startConsumer(forwarder)
+
+ forwarder.consume(consumer)
+
+ yield()
+
+ assertEquals(2.0, source.counters.actual)
+ assertEquals(source.counters.actual, forwarder.counters.actual) { "Actual work" }
+ assertEquals(source.counters.demand, forwarder.counters.demand) { "Work demand" }
+ assertEquals(source.counters.remaining, forwarder.counters.remaining) { "Overcommitted work" }
+ assertEquals(2000, clock.millis())
+ }
+
+ @Test
+ fun testCoupledExit() = runBlockingSimulation {
+ val engine = FlowEngineImpl(coroutineContext, clock)
+ val forwarder = FlowForwarder(engine, isCoupled = true)
+ val source = FlowSink(engine, 2000.0)
+
+ launch { source.consume(forwarder) }
+
+ forwarder.consume(FixedFlowSource(2000.0, 1.0))
+
+ yield()
+
+ assertFalse(source.isActive)
+ }
+
+ @Test
+ fun testPullFailureCoupled() = runBlockingSimulation {
+ val engine = FlowEngineImpl(coroutineContext, clock)
+ val forwarder = FlowForwarder(engine, isCoupled = true)
+ val source = FlowSink(engine, 2000.0)
+
+ launch { source.consume(forwarder) }
+
+ try {
+ forwarder.consume(object : FlowSource {
+ override fun onPull(conn: FlowConnection, now: Long, delta: Long): Long {
+ throw IllegalStateException("Test")
+ }
+ })
+ } catch (cause: Throwable) {
+ // Ignore
+ }
+
+ yield()
+
+ assertFalse(source.isActive)
+ }
+
+ @Test
+ fun testStartFailure() = runBlockingSimulation {
+ val engine = FlowEngineImpl(coroutineContext, clock)
+ val forwarder = FlowForwarder(engine)
+ val source = FlowSink(engine, 2000.0)
+
+ launch { source.consume(forwarder) }
+
+ try {
+ forwarder.consume(object : FlowSource {
+ override fun onPull(conn: FlowConnection, now: Long, delta: Long): Long {
+ return Long.MAX_VALUE
+ }
+
+ override fun onStart(conn: FlowConnection, now: Long) {
+ throw IllegalStateException("Test")
+ }
+ })
+ } catch (cause: Throwable) {
+ // Ignore
+ }
+
+ yield()
+
+ assertTrue(source.isActive)
+ source.cancel()
+ }
+
+ @Test
+ fun testConvergeFailure() = runBlockingSimulation {
+ val engine = FlowEngineImpl(coroutineContext, clock)
+ val forwarder = FlowForwarder(engine)
+ val source = FlowSink(engine, 2000.0)
+
+ launch { source.consume(forwarder) }
+
+ try {
+ forwarder.consume(object : FlowSource {
+ override fun onStart(conn: FlowConnection, now: Long) {
+ conn.shouldSourceConverge = true
+ }
+
+ override fun onPull(conn: FlowConnection, now: Long, delta: Long): Long {
+ return Long.MAX_VALUE
+ }
+
+ override fun onConverge(conn: FlowConnection, now: Long, delta: Long) {
+ throw IllegalStateException("Test")
+ }
+ })
+ } catch (cause: Throwable) {
+ // Ignore
+ }
+
+ yield()
+
+ assertTrue(source.isActive)
+ source.cancel()
+ }
+}
diff --git a/opendc-simulator/opendc-simulator-flow/src/test/kotlin/org/opendc/simulator/flow/FlowSinkTest.kt b/opendc-simulator/opendc-simulator-flow/src/test/kotlin/org/opendc/simulator/flow/FlowSinkTest.kt
new file mode 100644
index 00000000..70c75864
--- /dev/null
+++ b/opendc-simulator/opendc-simulator-flow/src/test/kotlin/org/opendc/simulator/flow/FlowSinkTest.kt
@@ -0,0 +1,241 @@
+/*
+ * Copyright (c) 2021 AtLarge Research
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
+ * of this software and associated documentation files (the "Software"), to deal
+ * in the Software without restriction, including without limitation the rights
+ * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+ * copies of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included in all
+ * copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
+ * SOFTWARE.
+ */
+
+package org.opendc.simulator.flow
+
+import io.mockk.spyk
+import io.mockk.verify
+import kotlinx.coroutines.*
+import org.junit.jupiter.api.*
+import org.junit.jupiter.api.Assertions.assertEquals
+import org.opendc.simulator.core.runBlockingSimulation
+import org.opendc.simulator.flow.internal.FlowEngineImpl
+import org.opendc.simulator.flow.source.FixedFlowSource
+import org.opendc.simulator.flow.source.FlowSourceRateAdapter
+
+/**
+ * A test suite for the [FlowSink] class.
+ */
+internal class FlowSinkTest {
+ @Test
+ fun testSpeed() = runBlockingSimulation {
+ val engine = FlowEngineImpl(coroutineContext, clock)
+ val capacity = 4200.0
+ val provider = FlowSink(engine, capacity)
+
+ val consumer = FixedFlowSource(4200.0, 1.0)
+
+ val res = mutableListOf<Double>()
+ val adapter = FlowSourceRateAdapter(consumer, res::add)
+
+ provider.consume(adapter)
+
+ assertEquals(listOf(0.0, capacity, 0.0), res) { "Speed is reported correctly" }
+ }
+
+ @Test
+ fun testAdjustCapacity() = runBlockingSimulation {
+ val engine = FlowEngineImpl(coroutineContext, clock)
+ val provider = FlowSink(engine, 1.0)
+
+ val consumer = spyk(FixedFlowSource(2.0, 1.0))
+
+ coroutineScope {
+ launch { provider.consume(consumer) }
+ delay(1000)
+ provider.capacity = 0.5
+ }
+ assertEquals(3000, clock.millis())
+ verify(exactly = 3) { consumer.onPull(any(), any(), any()) }
+ }
+
+ @Test
+ fun testSpeedLimit() = runBlockingSimulation {
+ val engine = FlowEngineImpl(coroutineContext, clock)
+ val capacity = 4200.0
+ val provider = FlowSink(engine, capacity)
+
+ val consumer = FixedFlowSource(capacity, 2.0)
+
+ val res = mutableListOf<Double>()
+ val adapter = FlowSourceRateAdapter(consumer, res::add)
+
+ provider.consume(adapter)
+
+ assertEquals(listOf(0.0, capacity, 0.0), res) { "Speed is reported correctly" }
+ }
+
+ /**
+ * Test to see whether no infinite recursion occurs when interrupting during [FlowSource.onStart] or
+ * [FlowSource.onPull].
+ */
+ @Test
+ fun testIntermediateInterrupt() = runBlockingSimulation {
+ val engine = FlowEngineImpl(coroutineContext, clock)
+ val capacity = 4200.0
+ val provider = FlowSink(engine, capacity)
+
+ val consumer = object : FlowSource {
+ override fun onPull(conn: FlowConnection, now: Long, delta: Long): Long {
+ conn.close()
+ return Long.MAX_VALUE
+ }
+
+ override fun onStart(conn: FlowConnection, now: Long) {
+ conn.pull()
+ }
+ }
+
+ provider.consume(consumer)
+ }
+
+ @Test
+ fun testInterrupt() = runBlockingSimulation {
+ val engine = FlowEngineImpl(coroutineContext, clock)
+ val capacity = 4200.0
+ val provider = FlowSink(engine, capacity)
+ lateinit var resCtx: FlowConnection
+
+ val consumer = object : FlowSource {
+ var isFirst = true
+
+ override fun onStart(conn: FlowConnection, now: Long) {
+ resCtx = conn
+ }
+
+ override fun onPull(conn: FlowConnection, now: Long, delta: Long): Long {
+ return if (isFirst) {
+ isFirst = false
+ conn.push(1.0)
+ 4000
+ } else {
+ conn.close()
+ Long.MAX_VALUE
+ }
+ }
+ }
+
+ launch {
+ yield()
+ resCtx.pull()
+ }
+ provider.consume(consumer)
+
+ assertEquals(0, clock.millis())
+ }
+
+ @Test
+ fun testFailure() = runBlockingSimulation {
+ val engine = FlowEngineImpl(coroutineContext, clock)
+ val capacity = 4200.0
+ val provider = FlowSink(engine, capacity)
+
+ val consumer = object : FlowSource {
+ override fun onStart(conn: FlowConnection, now: Long) {
+ throw IllegalStateException("Hi")
+ }
+
+ override fun onPull(conn: FlowConnection, now: Long, delta: Long): Long {
+ return Long.MAX_VALUE
+ }
+ }
+
+ assertThrows<IllegalStateException> {
+ provider.consume(consumer)
+ }
+ }
+
+ @Test
+ fun testExceptionPropagationOnNext() = runBlockingSimulation {
+ val engine = FlowEngineImpl(coroutineContext, clock)
+ val capacity = 4200.0
+ val provider = FlowSink(engine, capacity)
+
+ val consumer = object : FlowSource {
+ var isFirst = true
+
+ override fun onPull(conn: FlowConnection, now: Long, delta: Long): Long {
+ return if (isFirst) {
+ isFirst = false
+ conn.push(1.0)
+ 1000
+ } else {
+ throw IllegalStateException()
+ }
+ }
+ }
+
+ assertThrows<IllegalStateException> {
+ provider.consume(consumer)
+ }
+ }
+
+ @Test
+ fun testConcurrentConsumption() = runBlockingSimulation {
+ val engine = FlowEngineImpl(coroutineContext, clock)
+ val capacity = 4200.0
+ val provider = FlowSink(engine, capacity)
+
+ val consumer = FixedFlowSource(capacity, 1.0)
+
+ assertThrows<IllegalStateException> {
+ coroutineScope {
+ launch { provider.consume(consumer) }
+ provider.consume(consumer)
+ }
+ }
+ }
+
+ @Test
+ fun testCancelDuringConsumption() = runBlockingSimulation {
+ val engine = FlowEngineImpl(coroutineContext, clock)
+ val capacity = 4200.0
+ val provider = FlowSink(engine, capacity)
+
+ val consumer = FixedFlowSource(capacity, 1.0)
+
+ launch { provider.consume(consumer) }
+ delay(500)
+ provider.cancel()
+
+ yield()
+
+ assertEquals(500, clock.millis())
+ }
+
+ @Test
+ fun testInfiniteSleep() {
+ assertThrows<IllegalStateException> {
+ runBlockingSimulation {
+ val engine = FlowEngineImpl(coroutineContext, clock)
+ val capacity = 4200.0
+ val provider = FlowSink(engine, capacity)
+
+ val consumer = object : FlowSource {
+ override fun onPull(conn: FlowConnection, now: Long, delta: Long): Long = Long.MAX_VALUE
+ }
+
+ provider.consume(consumer)
+ }
+ }
+ }
+}
diff --git a/opendc-simulator/opendc-simulator-flow/src/test/kotlin/org/opendc/simulator/flow/mux/ForwardingFlowMultiplexerTest.kt b/opendc-simulator/opendc-simulator-flow/src/test/kotlin/org/opendc/simulator/flow/mux/ForwardingFlowMultiplexerTest.kt
new file mode 100644
index 00000000..187dacd9
--- /dev/null
+++ b/opendc-simulator/opendc-simulator-flow/src/test/kotlin/org/opendc/simulator/flow/mux/ForwardingFlowMultiplexerTest.kt
@@ -0,0 +1,154 @@
+/*
+ * Copyright (c) 2021 AtLarge Research
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
+ * of this software and associated documentation files (the "Software"), to deal
+ * in the Software without restriction, including without limitation the rights
+ * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+ * copies of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included in all
+ * copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
+ * SOFTWARE.
+ */
+
+package org.opendc.simulator.flow.mux
+
+import kotlinx.coroutines.yield
+import org.junit.jupiter.api.Assertions.assertEquals
+import org.junit.jupiter.api.Test
+import org.junit.jupiter.api.assertAll
+import org.junit.jupiter.api.assertThrows
+import org.opendc.simulator.core.runBlockingSimulation
+import org.opendc.simulator.flow.*
+import org.opendc.simulator.flow.internal.FlowEngineImpl
+import org.opendc.simulator.flow.source.FixedFlowSource
+import org.opendc.simulator.flow.source.FlowSourceRateAdapter
+import org.opendc.simulator.flow.source.TraceFlowSource
+
+/**
+ * Test suite for the [ForwardingFlowMultiplexer] class.
+ */
+internal class ForwardingFlowMultiplexerTest {
+ /**
+ * Test a trace workload.
+ */
+ @Test
+ fun testTrace() = runBlockingSimulation {
+ val engine = FlowEngineImpl(coroutineContext, clock)
+
+ val speed = mutableListOf<Double>()
+
+ val duration = 5 * 60L
+ val workload =
+ TraceFlowSource(
+ sequenceOf(
+ TraceFlowSource.Fragment(duration * 1000, 28.0),
+ TraceFlowSource.Fragment(duration * 1000, 3500.0),
+ TraceFlowSource.Fragment(duration * 1000, 0.0),
+ TraceFlowSource.Fragment(duration * 1000, 183.0)
+ ),
+ )
+
+ val switch = ForwardingFlowMultiplexer(engine)
+ val source = FlowSink(engine, 3200.0)
+ val forwarder = FlowForwarder(engine)
+ val adapter = FlowSourceRateAdapter(forwarder, speed::add)
+ source.startConsumer(adapter)
+ forwarder.startConsumer(switch.newOutput())
+
+ val provider = switch.newInput()
+ provider.consume(workload)
+ yield()
+
+ assertAll(
+ { assertEquals(listOf(0.0, 28.0, 3200.0, 0.0, 183.0, 0.0), speed) { "Correct speed" } },
+ { assertEquals(5 * 60L * 4000, clock.millis()) { "Took enough time" } }
+ )
+ }
+
+ /**
+ * Test runtime workload on hypervisor.
+ */
+ @Test
+ fun testRuntimeWorkload() = runBlockingSimulation {
+ val engine = FlowEngineImpl(coroutineContext, clock)
+
+ val duration = 5 * 60L * 1000
+ val workload = FixedFlowSource(duration * 3.2, 1.0)
+
+ val switch = ForwardingFlowMultiplexer(engine)
+ val source = FlowSink(engine, 3200.0)
+
+ source.startConsumer(switch.newOutput())
+
+ val provider = switch.newInput()
+ provider.consume(workload)
+ yield()
+
+ assertEquals(duration, clock.millis()) { "Took enough time" }
+ }
+
+ /**
+ * Test two workloads running sequentially.
+ */
+ @Test
+ fun testTwoWorkloads() = runBlockingSimulation {
+ val engine = FlowEngineImpl(coroutineContext, clock)
+
+ val duration = 5 * 60L * 1000
+ val workload = object : FlowSource {
+ var isFirst = true
+
+ override fun onStart(conn: FlowConnection, now: Long) {
+ isFirst = true
+ }
+
+ override fun onPull(conn: FlowConnection, now: Long, delta: Long): Long {
+ return if (isFirst) {
+ isFirst = false
+ conn.push(1.0)
+ duration
+ } else {
+ conn.close()
+ Long.MAX_VALUE
+ }
+ }
+ }
+
+ val switch = ForwardingFlowMultiplexer(engine)
+ val source = FlowSink(engine, 3200.0)
+
+ source.startConsumer(switch.newOutput())
+
+ val provider = switch.newInput()
+ provider.consume(workload)
+ yield()
+ provider.consume(workload)
+ assertEquals(duration * 2, clock.millis()) { "Took enough time" }
+ }
+
+ /**
+ * Test concurrent workloads on the machine.
+ */
+ @Test
+ fun testConcurrentWorkloadFails() = runBlockingSimulation {
+ val engine = FlowEngineImpl(coroutineContext, clock)
+
+ val switch = ForwardingFlowMultiplexer(engine)
+ val source = FlowSink(engine, 3200.0)
+
+ source.startConsumer(switch.newOutput())
+
+ switch.newInput()
+ assertThrows<IllegalStateException> { switch.newInput() }
+ }
+}
diff --git a/opendc-simulator/opendc-simulator-resources/src/test/kotlin/org/opendc/simulator/resources/SimResourceSwitchMaxMinTest.kt b/opendc-simulator/opendc-simulator-flow/src/test/kotlin/org/opendc/simulator/flow/mux/MaxMinFlowMultiplexerTest.kt
index e4292ec0..6e2cdb98 100644
--- a/opendc-simulator/opendc-simulator-resources/src/test/kotlin/org/opendc/simulator/resources/SimResourceSwitchMaxMinTest.kt
+++ b/opendc-simulator/opendc-simulator-flow/src/test/kotlin/org/opendc/simulator/flow/mux/MaxMinFlowMultiplexerTest.kt
@@ -20,43 +20,40 @@
* SOFTWARE.
*/
-package org.opendc.simulator.resources
+package org.opendc.simulator.flow.mux
-import io.mockk.every
-import io.mockk.mockk
-import kotlinx.coroutines.ExperimentalCoroutinesApi
import kotlinx.coroutines.coroutineScope
import kotlinx.coroutines.launch
import kotlinx.coroutines.yield
import org.junit.jupiter.api.*
import org.junit.jupiter.api.Assertions.assertEquals
import org.opendc.simulator.core.runBlockingSimulation
-import org.opendc.simulator.resources.consumer.SimTraceConsumer
-import org.opendc.simulator.resources.impl.SimResourceInterpreterImpl
+import org.opendc.simulator.flow.FlowSink
+import org.opendc.simulator.flow.consume
+import org.opendc.simulator.flow.internal.FlowEngineImpl
+import org.opendc.simulator.flow.source.FixedFlowSource
+import org.opendc.simulator.flow.source.TraceFlowSource
/**
- * Test suite for the [SimResourceSwitch] implementations
+ * Test suite for the [FlowMultiplexer] implementations
*/
-@OptIn(ExperimentalCoroutinesApi::class)
-internal class SimResourceSwitchMaxMinTest {
+internal class MaxMinFlowMultiplexerTest {
@Test
fun testSmoke() = runBlockingSimulation {
- val scheduler = SimResourceInterpreterImpl(coroutineContext, clock)
- val switch = SimResourceSwitchMaxMin(scheduler)
+ val scheduler = FlowEngineImpl(coroutineContext, clock)
+ val switch = MaxMinFlowMultiplexer(scheduler)
- val sources = List(2) { SimResourceSource(2000.0, scheduler) }
- sources.forEach { switch.addInput(it) }
+ val sources = List(2) { FlowSink(scheduler, 2000.0) }
+ sources.forEach { it.startConsumer(switch.newOutput()) }
- val provider = switch.newOutput()
-
- val consumer = mockk<SimResourceConsumer>(relaxUnitFun = true)
- every { consumer.onNext(any()) } returns SimResourceCommand.Consume(1.0, 1.0) andThen SimResourceCommand.Exit
+ val provider = switch.newInput()
+ val consumer = FixedFlowSource(2000.0, 1.0)
try {
provider.consume(consumer)
yield()
} finally {
- switch.close()
+ switch.clear()
}
}
@@ -65,34 +62,35 @@ internal class SimResourceSwitchMaxMinTest {
*/
@Test
fun testOvercommittedSingle() = runBlockingSimulation {
- val scheduler = SimResourceInterpreterImpl(coroutineContext, clock)
+ val scheduler = FlowEngineImpl(coroutineContext, clock)
val duration = 5 * 60L
val workload =
- SimTraceConsumer(
+ TraceFlowSource(
sequenceOf(
- SimTraceConsumer.Fragment(duration * 1000, 28.0),
- SimTraceConsumer.Fragment(duration * 1000, 3500.0),
- SimTraceConsumer.Fragment(duration * 1000, 0.0),
- SimTraceConsumer.Fragment(duration * 1000, 183.0)
+ 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 = SimResourceSwitchMaxMin(scheduler)
- val provider = switch.newOutput()
+ val switch = MaxMinFlowMultiplexer(scheduler)
+ val sink = FlowSink(scheduler, 3200.0)
+ val provider = switch.newInput()
try {
- switch.addInput(SimResourceSource(3200.0, scheduler))
+ sink.startConsumer(switch.newOutput())
provider.consume(workload)
yield()
} finally {
- switch.close()
+ 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(90000.0, switch.counters.overcommit, "Overcommitted work does not match") },
+ { assertEquals(2816700.0, switch.counters.remaining, "Remaining capacity does not match") },
{ assertEquals(1200000, clock.millis()) }
)
}
@@ -102,34 +100,35 @@ internal class SimResourceSwitchMaxMinTest {
*/
@Test
fun testOvercommittedDual() = runBlockingSimulation {
- val scheduler = SimResourceInterpreterImpl(coroutineContext, clock)
+ val scheduler = FlowEngineImpl(coroutineContext, clock)
val duration = 5 * 60L
val workloadA =
- SimTraceConsumer(
+ TraceFlowSource(
sequenceOf(
- SimTraceConsumer.Fragment(duration * 1000, 28.0),
- SimTraceConsumer.Fragment(duration * 1000, 3500.0),
- SimTraceConsumer.Fragment(duration * 1000, 0.0),
- SimTraceConsumer.Fragment(duration * 1000, 183.0)
+ 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 =
- SimTraceConsumer(
+ TraceFlowSource(
sequenceOf(
- SimTraceConsumer.Fragment(duration * 1000, 28.0),
- SimTraceConsumer.Fragment(duration * 1000, 3100.0),
- SimTraceConsumer.Fragment(duration * 1000, 0.0),
- SimTraceConsumer.Fragment(duration * 1000, 73.0)
+ 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 = SimResourceSwitchMaxMin(scheduler)
- val providerA = switch.newOutput()
- val providerB = switch.newOutput()
+ val switch = MaxMinFlowMultiplexer(scheduler)
+ val sink = FlowSink(scheduler, 3200.0)
+ val providerA = switch.newInput()
+ val providerB = switch.newInput()
try {
- switch.addInput(SimResourceSource(3200.0, scheduler))
+ sink.startConsumer(switch.newOutput())
coroutineScope {
launch { providerA.consume(workloadA) }
@@ -138,12 +137,12 @@ internal class SimResourceSwitchMaxMinTest {
yield()
} finally {
- switch.close()
+ 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(1020000.0, switch.counters.overcommit, "Overcommitted work does not match") },
+ { assertEquals(2786400.0, switch.counters.remaining, "Remaining capacity does not match") },
{ assertEquals(1200000, clock.millis()) }
)
}
diff --git a/opendc-simulator/opendc-simulator-resources/src/test/kotlin/org/opendc/simulator/resources/SimWorkConsumerTest.kt b/opendc-simulator/opendc-simulator-flow/src/test/kotlin/org/opendc/simulator/flow/source/FixedFlowSourceTest.kt
index 42648cf1..8396d346 100644
--- a/opendc-simulator/opendc-simulator-resources/src/test/kotlin/org/opendc/simulator/resources/SimWorkConsumerTest.kt
+++ b/opendc-simulator/opendc-simulator-flow/src/test/kotlin/org/opendc/simulator/flow/source/FixedFlowSourceTest.kt
@@ -20,26 +20,25 @@
* SOFTWARE.
*/
-package org.opendc.simulator.resources
+package org.opendc.simulator.flow.source
-import kotlinx.coroutines.ExperimentalCoroutinesApi
import org.junit.jupiter.api.Assertions.assertEquals
import org.junit.jupiter.api.Test
import org.opendc.simulator.core.runBlockingSimulation
-import org.opendc.simulator.resources.consumer.SimWorkConsumer
-import org.opendc.simulator.resources.impl.SimResourceInterpreterImpl
+import org.opendc.simulator.flow.FlowSink
+import org.opendc.simulator.flow.consume
+import org.opendc.simulator.flow.internal.FlowEngineImpl
/**
- * A test suite for the [SimWorkConsumer] class.
+ * A test suite for the [FixedFlowSource] class.
*/
-@OptIn(ExperimentalCoroutinesApi::class)
-internal class SimWorkConsumerTest {
+internal class FixedFlowSourceTest {
@Test
fun testSmoke() = runBlockingSimulation {
- val scheduler = SimResourceInterpreterImpl(coroutineContext, clock)
- val provider = SimResourceSource(1.0, scheduler)
+ val scheduler = FlowEngineImpl(coroutineContext, clock)
+ val provider = FlowSink(scheduler, 1.0)
- val consumer = SimWorkConsumer(1.0, 1.0)
+ val consumer = FixedFlowSource(1.0, 1.0)
provider.consume(consumer)
assertEquals(1000, clock.millis())
@@ -47,10 +46,10 @@ internal class SimWorkConsumerTest {
@Test
fun testUtilization() = runBlockingSimulation {
- val scheduler = SimResourceInterpreterImpl(coroutineContext, clock)
- val provider = SimResourceSource(1.0, scheduler)
+ val scheduler = FlowEngineImpl(coroutineContext, clock)
+ val provider = FlowSink(scheduler, 1.0)
- val consumer = SimWorkConsumer(1.0, 0.5)
+ val consumer = FixedFlowSource(1.0, 0.5)
provider.consume(consumer)
assertEquals(2000, clock.millis())
diff --git a/opendc-simulator/opendc-simulator-network/build.gradle.kts b/opendc-simulator/opendc-simulator-network/build.gradle.kts
index eb9adcd1..f8931053 100644
--- a/opendc-simulator/opendc-simulator-network/build.gradle.kts
+++ b/opendc-simulator/opendc-simulator-network/build.gradle.kts
@@ -30,6 +30,8 @@ plugins {
dependencies {
api(platform(projects.opendcPlatform))
- api(projects.opendcSimulator.opendcSimulatorResources)
+ api(projects.opendcSimulator.opendcSimulatorFlow)
implementation(projects.opendcSimulator.opendcSimulatorCore)
+
+ testImplementation(libs.slf4j.simple)
}
diff --git a/opendc-simulator/opendc-simulator-network/src/main/kotlin/org/opendc/simulator/network/SimNetworkPort.kt b/opendc-simulator/opendc-simulator-network/src/main/kotlin/org/opendc/simulator/network/SimNetworkPort.kt
index 102e5625..4b66d5cf 100644
--- 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
@@ -22,8 +22,8 @@
package org.opendc.simulator.network
-import org.opendc.simulator.resources.SimResourceConsumer
-import org.opendc.simulator.resources.SimResourceProvider
+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.
@@ -78,14 +78,14 @@ public abstract class SimNetworkPort {
}
/**
- * Create a [SimResourceConsumer] which generates the outgoing traffic of this port.
+ * Create a [FlowSource] which generates the outgoing traffic of this port.
*/
- protected abstract fun createConsumer(): SimResourceConsumer
+ protected abstract fun createConsumer(): FlowSource
/**
- * The [SimResourceProvider] which processes the ingoing traffic of this port.
+ * The [FlowConsumer] which processes the ingoing traffic of this port.
*/
- protected abstract val provider: SimResourceProvider
+ protected abstract val provider: FlowConsumer
override fun toString(): String = "SimNetworkPort[isConnected=$isConnected]"
}
diff --git a/opendc-simulator/opendc-simulator-network/src/main/kotlin/org/opendc/simulator/network/SimNetworkSink.kt b/opendc-simulator/opendc-simulator-network/src/main/kotlin/org/opendc/simulator/network/SimNetworkSink.kt
index 5efdbed9..4b0d7bbd 100644
--- a/opendc-simulator/opendc-simulator-network/src/main/kotlin/org/opendc/simulator/network/SimNetworkSink.kt
+++ b/opendc-simulator/opendc-simulator-network/src/main/kotlin/org/opendc/simulator/network/SimNetworkSink.kt
@@ -22,22 +22,22 @@
package org.opendc.simulator.network
-import org.opendc.simulator.resources.*
+import org.opendc.simulator.flow.*
/**
* A network sink which discards all received traffic and does not generate any traffic itself.
*/
public class SimNetworkSink(
- interpreter: SimResourceInterpreter,
+ engine: FlowEngine,
public val capacity: Double
) : SimNetworkPort() {
- override fun createConsumer(): SimResourceConsumer = object : SimResourceConsumer {
- override fun onNext(ctx: SimResourceContext): SimResourceCommand = SimResourceCommand.Idle()
+ override fun createConsumer(): FlowSource = object : FlowSource {
+ override fun onPull(conn: FlowConnection, now: Long, delta: Long): Long = Long.MAX_VALUE
override fun toString(): String = "SimNetworkSink.Consumer"
}
- override val provider: SimResourceProvider = SimResourceSource(capacity, interpreter)
+ override val provider: FlowConsumer = FlowSink(engine, capacity)
override fun toString(): String = "SimNetworkSink[capacity=$capacity]"
}
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
index 05daaa5c..6667c80c 100644
--- 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
@@ -22,12 +22,13 @@
package org.opendc.simulator.network
-import org.opendc.simulator.resources.*
+import org.opendc.simulator.flow.*
+import org.opendc.simulator.flow.mux.MaxMinFlowMultiplexer
/**
* A [SimNetworkSwitch] that can support new networking ports on demand.
*/
-public class SimNetworkSwitchVirtual(interpreter: SimResourceInterpreter) : SimNetworkSwitch {
+public class SimNetworkSwitchVirtual(private val engine: FlowEngine) : SimNetworkSwitch {
/**
* The ports of this switch.
*/
@@ -36,9 +37,9 @@ public class SimNetworkSwitchVirtual(interpreter: SimResourceInterpreter) : SimN
private val _ports = mutableListOf<Port>()
/**
- * The [SimResourceSwitchMaxMin] to actually perform the switching.
+ * The [MaxMinFlowMultiplexer] to actually perform the switching.
*/
- private val switch = SimResourceSwitchMaxMin(interpreter)
+ private val mux = MaxMinFlowMultiplexer(engine)
/**
* Open a new port on the switch.
@@ -58,19 +59,17 @@ public class SimNetworkSwitchVirtual(interpreter: SimResourceInterpreter) : SimN
*/
private var isClosed: Boolean = false
- override val provider: SimResourceProvider
+ override val provider: FlowConsumer
get() = _provider
- private val _provider = switch.newOutput()
+ private val _provider = mux.newInput()
- override fun createConsumer(): SimResourceConsumer {
- val forwarder = SimResourceForwarder(isCoupled = true)
- switch.addInput(forwarder)
- return forwarder
- }
+ private val _source = mux.newOutput()
+
+ override fun createConsumer(): FlowSource = _source
override fun close() {
isClosed = true
- _provider.close()
+ 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 b8c4b00d..14d22162 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
@@ -31,8 +31,8 @@ import org.junit.jupiter.api.Test
import org.junit.jupiter.api.assertDoesNotThrow
import org.junit.jupiter.api.assertThrows
import org.opendc.simulator.core.runBlockingSimulation
-import org.opendc.simulator.resources.*
-import org.opendc.simulator.resources.consumer.SimWorkConsumer
+import org.opendc.simulator.flow.*
+import org.opendc.simulator.flow.source.FixedFlowSource
/**
* Test suite for the [SimNetworkSink] class.
@@ -40,8 +40,8 @@ import org.opendc.simulator.resources.consumer.SimWorkConsumer
class SimNetworkSinkTest {
@Test
fun testInitialState() = runBlockingSimulation {
- val interpreter = SimResourceInterpreter(coroutineContext, clock)
- val sink = SimNetworkSink(interpreter, capacity = 100.0)
+ val engine = FlowEngine(coroutineContext, clock)
+ val sink = SimNetworkSink(engine, capacity = 100.0)
assertFalse(sink.isConnected)
assertNull(sink.link)
@@ -50,8 +50,8 @@ class SimNetworkSinkTest {
@Test
fun testDisconnectIdempotent() = runBlockingSimulation {
- val interpreter = SimResourceInterpreter(coroutineContext, clock)
- val sink = SimNetworkSink(interpreter, capacity = 100.0)
+ val engine = FlowEngine(coroutineContext, clock)
+ val sink = SimNetworkSink(engine, capacity = 100.0)
assertDoesNotThrow { sink.disconnect() }
assertFalse(sink.isConnected)
@@ -59,8 +59,8 @@ class SimNetworkSinkTest {
@Test
fun testConnectCircular() = runBlockingSimulation {
- val interpreter = SimResourceInterpreter(coroutineContext, clock)
- val sink = SimNetworkSink(interpreter, capacity = 100.0)
+ val engine = FlowEngine(coroutineContext, clock)
+ val sink = SimNetworkSink(engine, capacity = 100.0)
assertThrows<IllegalArgumentException> {
sink.connect(sink)
@@ -69,8 +69,8 @@ class SimNetworkSinkTest {
@Test
fun testConnectAlreadyConnectedTarget() = runBlockingSimulation {
- val interpreter = SimResourceInterpreter(coroutineContext, clock)
- val sink = SimNetworkSink(interpreter, capacity = 100.0)
+ val engine = FlowEngine(coroutineContext, clock)
+ val sink = SimNetworkSink(engine, capacity = 100.0)
val source = mockk<SimNetworkPort>(relaxUnitFun = true)
every { source.isConnected } returns true
@@ -81,9 +81,9 @@ class SimNetworkSinkTest {
@Test
fun testConnectAlreadyConnected() = runBlockingSimulation {
- val interpreter = SimResourceInterpreter(coroutineContext, clock)
- val sink = SimNetworkSink(interpreter, capacity = 100.0)
- val source1 = Source(interpreter)
+ val engine = FlowEngine(coroutineContext, clock)
+ val sink = SimNetworkSink(engine, capacity = 100.0)
+ val source1 = Source(engine)
val source2 = mockk<SimNetworkPort>(relaxUnitFun = true)
@@ -97,9 +97,9 @@ class SimNetworkSinkTest {
@Test
fun testConnect() = runBlockingSimulation {
- val interpreter = SimResourceInterpreter(coroutineContext, clock)
- val sink = SimNetworkSink(interpreter, capacity = 100.0)
- val source = spyk(Source(interpreter))
+ val engine = FlowEngine(coroutineContext, clock)
+ val sink = SimNetworkSink(engine, capacity = 100.0)
+ val source = spyk(Source(engine))
val consumer = source.consumer
sink.connect(source)
@@ -108,14 +108,14 @@ class SimNetworkSinkTest {
assertTrue(source.isConnected)
verify { source.createConsumer() }
- verify { consumer.onEvent(any(), SimResourceEvent.Start) }
+ verify { consumer.onStart(any(), any()) }
}
@Test
fun testDisconnect() = runBlockingSimulation {
- val interpreter = SimResourceInterpreter(coroutineContext, clock)
- val sink = SimNetworkSink(interpreter, capacity = 100.0)
- val source = spyk(Source(interpreter))
+ val engine = FlowEngine(coroutineContext, clock)
+ val sink = SimNetworkSink(engine, capacity = 100.0)
+ val source = spyk(Source(engine))
val consumer = source.consumer
sink.connect(source)
@@ -124,14 +124,14 @@ class SimNetworkSinkTest {
assertFalse(sink.isConnected)
assertFalse(source.isConnected)
- verify { consumer.onEvent(any(), SimResourceEvent.Exit) }
+ verify { consumer.onStop(any(), any(), any()) }
}
- private class Source(interpreter: SimResourceInterpreter) : SimNetworkPort() {
- val consumer = spyk(SimWorkConsumer(Double.POSITIVE_INFINITY, utilization = 0.8))
+ private class Source(engine: FlowEngine) : SimNetworkPort() {
+ val consumer = spyk(FixedFlowSource(Double.POSITIVE_INFINITY, utilization = 0.8))
- public override fun createConsumer(): SimResourceConsumer = consumer
+ public override fun createConsumer(): FlowSource = consumer
- override val provider: SimResourceProvider = SimResourceSource(0.0, interpreter)
+ override val provider: FlowConsumer = FlowSink(engine, 0.0)
}
}
diff --git a/opendc-simulator/opendc-simulator-network/src/test/kotlin/org/opendc/simulator/network/SimNetworkSwitchVirtualTest.kt b/opendc-simulator/opendc-simulator-network/src/test/kotlin/org/opendc/simulator/network/SimNetworkSwitchVirtualTest.kt
index 3a749bfe..62e54ffb 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
@@ -28,8 +28,8 @@ import org.junit.jupiter.api.Assertions.assertTrue
import org.junit.jupiter.api.Test
import org.junit.jupiter.api.assertThrows
import org.opendc.simulator.core.runBlockingSimulation
-import org.opendc.simulator.resources.*
-import org.opendc.simulator.resources.consumer.SimWorkConsumer
+import org.opendc.simulator.flow.*
+import org.opendc.simulator.flow.source.FixedFlowSource
/**
* Test suite for the [SimNetworkSwitchVirtual] class.
@@ -37,10 +37,10 @@ import org.opendc.simulator.resources.consumer.SimWorkConsumer
class SimNetworkSwitchVirtualTest {
@Test
fun testConnect() = runBlockingSimulation {
- val interpreter = SimResourceInterpreter(coroutineContext, clock)
- val sink = SimNetworkSink(interpreter, capacity = 100.0)
- val source = spyk(Source(interpreter))
- val switch = SimNetworkSwitchVirtual(interpreter)
+ val engine = FlowEngine(coroutineContext, clock)
+ val sink = SimNetworkSink(engine, capacity = 100.0)
+ val source = spyk(Source(engine))
+ val switch = SimNetworkSwitchVirtual(engine)
val consumer = source.consumer
switch.newPort().connect(sink)
@@ -50,14 +50,14 @@ class SimNetworkSwitchVirtualTest {
assertTrue(source.isConnected)
verify { source.createConsumer() }
- verify { consumer.onEvent(any(), SimResourceEvent.Start) }
+ verify { consumer.onStart(any(), any()) }
}
@Test
fun testConnectClosedPort() = runBlockingSimulation {
- val interpreter = SimResourceInterpreter(coroutineContext, clock)
- val sink = SimNetworkSink(interpreter, capacity = 100.0)
- val switch = SimNetworkSwitchVirtual(interpreter)
+ val engine = FlowEngine(coroutineContext, clock)
+ val sink = SimNetworkSink(engine, capacity = 100.0)
+ val switch = SimNetworkSwitchVirtual(engine)
val port = switch.newPort()
port.close()
@@ -67,11 +67,11 @@ class SimNetworkSwitchVirtualTest {
}
}
- private class Source(interpreter: SimResourceInterpreter) : SimNetworkPort() {
- val consumer = spyk(SimWorkConsumer(Double.POSITIVE_INFINITY, utilization = 0.8))
+ private class Source(engine: FlowEngine) : SimNetworkPort() {
+ val consumer = spyk(FixedFlowSource(Double.POSITIVE_INFINITY, utilization = 0.8))
- public override fun createConsumer(): SimResourceConsumer = consumer
+ public override fun createConsumer(): FlowSource = consumer
- override val provider: SimResourceProvider = SimResourceSource(0.0, interpreter)
+ override val provider: FlowConsumer = FlowSink(engine, 0.0)
}
}
diff --git a/opendc-simulator/opendc-simulator-power/build.gradle.kts b/opendc-simulator/opendc-simulator-power/build.gradle.kts
index f2a49964..5d8c8949 100644
--- a/opendc-simulator/opendc-simulator-power/build.gradle.kts
+++ b/opendc-simulator/opendc-simulator-power/build.gradle.kts
@@ -30,6 +30,8 @@ plugins {
dependencies {
api(platform(projects.opendcPlatform))
- api(projects.opendcSimulator.opendcSimulatorResources)
+ api(projects.opendcSimulator.opendcSimulatorFlow)
implementation(projects.opendcSimulator.opendcSimulatorCore)
+
+ testImplementation(libs.slf4j.simple)
}
diff --git a/opendc-simulator/opendc-simulator-power/src/main/kotlin/org/opendc/simulator/power/SimPdu.kt b/opendc-simulator/opendc-simulator-power/src/main/kotlin/org/opendc/simulator/power/SimPdu.kt
index 3ce85d02..9f88fecc 100644
--- 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
@@ -22,52 +22,40 @@
package org.opendc.simulator.power
-import org.opendc.simulator.resources.*
+import org.opendc.simulator.flow.*
+import org.opendc.simulator.flow.mux.FlowMultiplexer
+import org.opendc.simulator.flow.mux.MaxMinFlowMultiplexer
/**
* A model of a Power Distribution Unit (PDU).
*
- * @param interpreter The underlying [SimResourceInterpreter] to drive the simulation under the hood.
+ * @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(
- interpreter: SimResourceInterpreter,
+ engine: FlowEngine,
private val idlePower: Double = 0.0,
private val lossCoefficient: Double = 0.0,
) : SimPowerInlet() {
/**
- * The [SimResourceDistributor] that distributes the electricity over the PDU outlets.
+ * The [FlowMultiplexer] that distributes the electricity over the PDU outlets.
*/
- private val distributor = SimResourceDistributorMaxMin(interpreter)
+ private val mux = MaxMinFlowMultiplexer(engine)
/**
- * Create a new PDU outlet.
+ * The [FlowForwarder] that represents the input of the PDU.
*/
- public fun newOutlet(): Outlet = Outlet(distributor.newOutput())
-
- override fun createConsumer(): SimResourceConsumer = object : SimResourceConsumer by distributor {
- override fun onNext(ctx: SimResourceContext): SimResourceCommand {
- return when (val cmd = distributor.onNext(ctx)) {
- is SimResourceCommand.Consume -> {
- val duration = cmd.work / cmd.limit
- val loss = computePowerLoss(cmd.limit)
- val newLimit = cmd.limit + loss
+ private val output = mux.newOutput()
- SimResourceCommand.Consume(duration * newLimit, newLimit, cmd.deadline)
- }
- is SimResourceCommand.Idle -> {
- val loss = computePowerLoss(0.0)
- if (loss > 0.0)
- SimResourceCommand.Consume(Double.POSITIVE_INFINITY, loss, cmd.deadline)
- else
- cmd
- }
- else -> cmd
- }
- }
+ /**
+ * Create a new PDU outlet.
+ */
+ public fun newOutlet(): Outlet = Outlet(mux, mux.newInput())
- override fun toString(): String = "SimPdu.Consumer"
+ override fun createSource(): FlowSource = FlowMapper(output) { _, rate ->
+ val loss = computePowerLoss(rate)
+ rate + loss
}
override fun toString(): String = "SimPdu"
@@ -83,9 +71,9 @@ public class SimPdu(
/**
* A PDU outlet.
*/
- public class Outlet(private val provider: SimResourceCloseableProvider) : SimPowerOutlet(), AutoCloseable {
+ public class Outlet(private val switch: FlowMultiplexer, private val provider: FlowConsumer) : SimPowerOutlet(), AutoCloseable {
override fun onConnect(inlet: SimPowerInlet) {
- provider.startConsumer(inlet.createConsumer())
+ provider.startConsumer(inlet.createSource())
}
override fun onDisconnect(inlet: SimPowerInlet) {
@@ -96,7 +84,7 @@ public class SimPdu(
* Remove the outlet from the PDU.
*/
override fun close() {
- provider.close()
+ switch.removeInput(provider)
}
override fun toString(): String = "SimPdu.Outlet"
diff --git a/opendc-simulator/opendc-simulator-power/src/main/kotlin/org/opendc/simulator/power/SimPowerInlet.kt b/opendc-simulator/opendc-simulator-power/src/main/kotlin/org/opendc/simulator/power/SimPowerInlet.kt
index 0ac1f199..de587b7f 100644
--- a/opendc-simulator/opendc-simulator-power/src/main/kotlin/org/opendc/simulator/power/SimPowerInlet.kt
+++ b/opendc-simulator/opendc-simulator-power/src/main/kotlin/org/opendc/simulator/power/SimPowerInlet.kt
@@ -22,7 +22,7 @@
package org.opendc.simulator.power
-import org.opendc.simulator.resources.SimResourceConsumer
+import org.opendc.simulator.flow.FlowSource
/**
* An abstract inlet that consumes electricity from a power outlet.
@@ -42,7 +42,7 @@ public abstract class SimPowerInlet {
internal var _outlet: SimPowerOutlet? = null
/**
- * Create a [SimResourceConsumer] which represents the consumption of electricity from the power outlet.
+ * Create a [FlowSource] which represents the consumption of electricity from the power outlet.
*/
- public abstract fun createConsumer(): SimResourceConsumer
+ public abstract fun createSource(): FlowSource
}
diff --git a/opendc-simulator/opendc-simulator-power/src/main/kotlin/org/opendc/simulator/power/SimPowerSource.kt b/opendc-simulator/opendc-simulator-power/src/main/kotlin/org/opendc/simulator/power/SimPowerSource.kt
index 3ef8ccc6..07e9f52e 100644
--- a/opendc-simulator/opendc-simulator-power/src/main/kotlin/org/opendc/simulator/power/SimPowerSource.kt
+++ b/opendc-simulator/opendc-simulator-power/src/main/kotlin/org/opendc/simulator/power/SimPowerSource.kt
@@ -22,28 +22,28 @@
package org.opendc.simulator.power
-import org.opendc.simulator.resources.SimResourceInterpreter
-import org.opendc.simulator.resources.SimResourceSource
+import org.opendc.simulator.flow.FlowEngine
+import org.opendc.simulator.flow.FlowSink
/**
* A [SimPowerOutlet] that represents a source of electricity.
*
- * @param interpreter The underlying [SimResourceInterpreter] to drive the simulation under the hood.
+ * @param engine The underlying [FlowEngine] to drive the simulation under the hood.
*/
-public class SimPowerSource(interpreter: SimResourceInterpreter, public val capacity: Double) : SimPowerOutlet() {
+public class SimPowerSource(engine: FlowEngine, public val capacity: Double) : SimPowerOutlet() {
/**
* The resource source that drives this power source.
*/
- private val source = SimResourceSource(capacity, interpreter)
+ private val source = FlowSink(engine, capacity)
/**
* The power draw at this instant.
*/
public val powerDraw: Double
- get() = source.speed
+ get() = source.rate
override fun onConnect(inlet: SimPowerInlet) {
- source.startConsumer(inlet.createConsumer())
+ source.startConsumer(inlet.createSource())
}
override 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
index f9431d21..46d659f8 100644
--- 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
@@ -22,63 +22,54 @@
package org.opendc.simulator.power
-import org.opendc.simulator.resources.*
+import org.opendc.simulator.flow.*
+import org.opendc.simulator.flow.mux.MaxMinFlowMultiplexer
/**
* A model of an Uninterruptible Power Supply (UPS).
*
* This model aggregates multiple power sources into a single source in order to ensure that power is always available.
*
- * @param interpreter The underlying [SimResourceInterpreter] to drive the simulation under the hood.
+ * @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(
- interpreter: SimResourceInterpreter,
+ 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 aggregator = SimResourceAggregatorMaxMin(interpreter)
+ 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 = SimResourceForwarder(isCoupled = true)
- aggregator.addInput(forward)
+ val forward = FlowForwarder(engine, isCoupled = true)
+ forward.startConsumer(mux.newOutput())
return Inlet(forward)
}
override fun onConnect(inlet: SimPowerInlet) {
- val consumer = inlet.createConsumer()
- aggregator.startConsumer(object : SimResourceConsumer by consumer {
- override fun onNext(ctx: SimResourceContext): SimResourceCommand {
- return when (val cmd = consumer.onNext(ctx)) {
- is SimResourceCommand.Consume -> {
- val duration = cmd.work / cmd.limit
- val loss = computePowerLoss(cmd.limit)
- val newLimit = cmd.limit + loss
+ val source = inlet.createSource()
+ val mapper = FlowMapper(source) { _, rate ->
+ val loss = computePowerLoss(rate)
+ rate + loss
+ }
- SimResourceCommand.Consume(duration * newLimit, newLimit, cmd.deadline)
- }
- is SimResourceCommand.Idle -> {
- val loss = computePowerLoss(0.0)
- if (loss > 0.0)
- SimResourceCommand.Consume(Double.POSITIVE_INFINITY, loss, cmd.deadline)
- else
- cmd
- }
- else -> cmd
- }
- }
- })
+ provider.startConsumer(mapper)
}
override fun onDisconnect(inlet: SimPowerInlet) {
- aggregator.cancel()
+ provider.cancel()
}
/**
@@ -92,8 +83,8 @@ public class SimUps(
/**
* A UPS inlet.
*/
- public inner class Inlet(private val forwarder: SimResourceTransformer) : SimPowerInlet(), AutoCloseable {
- override fun createConsumer(): SimResourceConsumer = forwarder
+ public inner class Inlet(private val forwarder: FlowForwarder) : SimPowerInlet(), AutoCloseable {
+ override fun createSource(): FlowSource = forwarder
/**
* Remove the inlet from the PSU.
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 17a174b7..eb823eb1 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
@@ -28,10 +28,9 @@ import org.junit.jupiter.api.Assertions.assertEquals
import org.junit.jupiter.api.Test
import org.junit.jupiter.api.assertThrows
import org.opendc.simulator.core.runBlockingSimulation
-import org.opendc.simulator.resources.SimResourceConsumer
-import org.opendc.simulator.resources.SimResourceEvent
-import org.opendc.simulator.resources.SimResourceInterpreter
-import org.opendc.simulator.resources.consumer.SimWorkConsumer
+import org.opendc.simulator.flow.FlowEngine
+import org.opendc.simulator.flow.FlowSource
+import org.opendc.simulator.flow.source.FixedFlowSource
/**
* Test suite for the [SimPdu] class.
@@ -39,9 +38,9 @@ import org.opendc.simulator.resources.consumer.SimWorkConsumer
internal class SimPduTest {
@Test
fun testZeroOutlets() = runBlockingSimulation {
- val interpreter = SimResourceInterpreter(coroutineContext, clock)
- val source = SimPowerSource(interpreter, capacity = 100.0)
- val pdu = SimPdu(interpreter)
+ val engine = FlowEngine(coroutineContext, clock)
+ val source = SimPowerSource(engine, capacity = 100.0)
+ val pdu = SimPdu(engine)
source.connect(pdu)
assertEquals(0.0, source.powerDraw)
@@ -49,9 +48,9 @@ internal class SimPduTest {
@Test
fun testSingleOutlet() = runBlockingSimulation {
- val interpreter = SimResourceInterpreter(coroutineContext, clock)
- val source = SimPowerSource(interpreter, capacity = 100.0)
- val pdu = SimPdu(interpreter)
+ val engine = FlowEngine(coroutineContext, clock)
+ val source = SimPowerSource(engine, capacity = 100.0)
+ val pdu = SimPdu(engine)
source.connect(pdu)
pdu.newOutlet().connect(SimpleInlet())
@@ -60,9 +59,9 @@ internal class SimPduTest {
@Test
fun testDoubleOutlet() = runBlockingSimulation {
- val interpreter = SimResourceInterpreter(coroutineContext, clock)
- val source = SimPowerSource(interpreter, capacity = 100.0)
- val pdu = SimPdu(interpreter)
+ val engine = FlowEngine(coroutineContext, clock)
+ val source = SimPowerSource(engine, capacity = 100.0)
+ val pdu = SimPdu(engine)
source.connect(pdu)
pdu.newOutlet().connect(SimpleInlet())
@@ -73,28 +72,28 @@ internal class SimPduTest {
@Test
fun testDisconnect() = runBlockingSimulation {
- val interpreter = SimResourceInterpreter(coroutineContext, clock)
- val source = SimPowerSource(interpreter, capacity = 100.0)
- val pdu = SimPdu(interpreter)
+ val engine = FlowEngine(coroutineContext, clock)
+ val source = SimPowerSource(engine, capacity = 100.0)
+ val pdu = SimPdu(engine)
source.connect(pdu)
- val consumer = spyk(SimWorkConsumer(100.0, utilization = 1.0))
+ val consumer = spyk(FixedFlowSource(100.0, utilization = 1.0))
val inlet = object : SimPowerInlet() {
- override fun createConsumer(): SimResourceConsumer = consumer
+ override fun createSource(): FlowSource = consumer
}
val outlet = pdu.newOutlet()
outlet.connect(inlet)
outlet.disconnect()
- verify { consumer.onEvent(any(), SimResourceEvent.Exit) }
+ verify { consumer.onStop(any(), any(), any()) }
}
@Test
fun testLoss() = runBlockingSimulation {
- val interpreter = SimResourceInterpreter(coroutineContext, clock)
- val source = SimPowerSource(interpreter, capacity = 100.0)
+ val engine = FlowEngine(coroutineContext, clock)
+ val source = SimPowerSource(engine, capacity = 100.0)
// https://download.schneider-electric.com/files?p_Doc_Ref=SPD_NRAN-66CK3D_EN
- val pdu = SimPdu(interpreter, idlePower = 1.5, lossCoefficient = 0.015)
+ val pdu = SimPdu(engine, idlePower = 1.5, lossCoefficient = 0.015)
source.connect(pdu)
pdu.newOutlet().connect(SimpleInlet())
assertEquals(89.0, source.powerDraw, 0.01)
@@ -102,9 +101,9 @@ internal class SimPduTest {
@Test
fun testOutletClose() = runBlockingSimulation {
- val interpreter = SimResourceInterpreter(coroutineContext, clock)
- val source = SimPowerSource(interpreter, capacity = 100.0)
- val pdu = SimPdu(interpreter)
+ val engine = FlowEngine(coroutineContext, clock)
+ val source = SimPowerSource(engine, capacity = 100.0)
+ val pdu = SimPdu(engine)
source.connect(pdu)
val outlet = pdu.newOutlet()
outlet.close()
@@ -115,6 +114,6 @@ internal class SimPduTest {
}
class SimpleInlet : SimPowerInlet() {
- override fun createConsumer(): SimResourceConsumer = SimWorkConsumer(100.0, utilization = 0.5)
+ 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 f3829ba1..76142103 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
@@ -31,10 +31,9 @@ import org.junit.jupiter.api.Test
import org.junit.jupiter.api.assertDoesNotThrow
import org.junit.jupiter.api.assertThrows
import org.opendc.simulator.core.runBlockingSimulation
-import org.opendc.simulator.resources.SimResourceConsumer
-import org.opendc.simulator.resources.SimResourceEvent
-import org.opendc.simulator.resources.SimResourceInterpreter
-import org.opendc.simulator.resources.consumer.SimWorkConsumer
+import org.opendc.simulator.flow.FlowEngine
+import org.opendc.simulator.flow.FlowSource
+import org.opendc.simulator.flow.source.FixedFlowSource
/**
* Test suite for the [SimPowerSource]
@@ -42,8 +41,8 @@ import org.opendc.simulator.resources.consumer.SimWorkConsumer
internal class SimPowerSourceTest {
@Test
fun testInitialState() = runBlockingSimulation {
- val interpreter = SimResourceInterpreter(coroutineContext, clock)
- val source = SimPowerSource(interpreter, capacity = 100.0)
+ val engine = FlowEngine(coroutineContext, clock)
+ val source = SimPowerSource(engine, capacity = 100.0)
assertFalse(source.isConnected)
assertNull(source.inlet)
@@ -52,8 +51,8 @@ internal class SimPowerSourceTest {
@Test
fun testDisconnectIdempotent() = runBlockingSimulation {
- val interpreter = SimResourceInterpreter(coroutineContext, clock)
- val source = SimPowerSource(interpreter, capacity = 100.0)
+ val engine = FlowEngine(coroutineContext, clock)
+ val source = SimPowerSource(engine, capacity = 100.0)
assertDoesNotThrow { source.disconnect() }
assertFalse(source.isConnected)
@@ -61,8 +60,8 @@ internal class SimPowerSourceTest {
@Test
fun testConnect() = runBlockingSimulation {
- val interpreter = SimResourceInterpreter(coroutineContext, clock)
- val source = SimPowerSource(interpreter, capacity = 100.0)
+ val engine = FlowEngine(coroutineContext, clock)
+ val source = SimPowerSource(engine, capacity = 100.0)
val inlet = SimpleInlet()
source.connect(inlet)
@@ -76,27 +75,27 @@ internal class SimPowerSourceTest {
@Test
fun testDisconnect() = runBlockingSimulation {
- val interpreter = SimResourceInterpreter(coroutineContext, clock)
- val source = SimPowerSource(interpreter, capacity = 100.0)
- val consumer = spyk(SimWorkConsumer(100.0, utilization = 1.0))
+ 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 createConsumer(): SimResourceConsumer = consumer
+ override fun createSource(): FlowSource = consumer
}
source.connect(inlet)
source.disconnect()
- verify { consumer.onEvent(any(), SimResourceEvent.Exit) }
+ verify { consumer.onStop(any(), any(), any()) }
}
@Test
fun testDisconnectAssertion() = runBlockingSimulation {
- val interpreter = SimResourceInterpreter(coroutineContext, clock)
- val source = SimPowerSource(interpreter, capacity = 100.0)
+ val engine = FlowEngine(coroutineContext, clock)
+ val source = SimPowerSource(engine, capacity = 100.0)
val inlet = mockk<SimPowerInlet>(relaxUnitFun = true)
every { inlet.isConnected } returns false
every { inlet._outlet } returns null
- every { inlet.createConsumer() } returns SimWorkConsumer(100.0, utilization = 1.0)
+ every { inlet.createSource() } returns FixedFlowSource(100.0, utilization = 1.0)
source.connect(inlet)
@@ -107,8 +106,8 @@ internal class SimPowerSourceTest {
@Test
fun testOutletAlreadyConnected() = runBlockingSimulation {
- val interpreter = SimResourceInterpreter(coroutineContext, clock)
- val source = SimPowerSource(interpreter, capacity = 100.0)
+ val engine = FlowEngine(coroutineContext, clock)
+ val source = SimPowerSource(engine, capacity = 100.0)
val inlet = SimpleInlet()
source.connect(inlet)
@@ -121,8 +120,8 @@ internal class SimPowerSourceTest {
@Test
fun testInletAlreadyConnected() = runBlockingSimulation {
- val interpreter = SimResourceInterpreter(coroutineContext, clock)
- val source = SimPowerSource(interpreter, capacity = 100.0)
+ val engine = FlowEngine(coroutineContext, clock)
+ val source = SimPowerSource(engine, capacity = 100.0)
val inlet = mockk<SimPowerInlet>(relaxUnitFun = true)
every { inlet.isConnected } returns true
@@ -132,6 +131,6 @@ internal class SimPowerSourceTest {
}
class SimpleInlet : SimPowerInlet() {
- override fun createConsumer(): SimResourceConsumer = SimWorkConsumer(100.0, utilization = 1.0)
+ 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 8d5fa857..a764a368 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
@@ -28,10 +28,9 @@ import org.junit.jupiter.api.Assertions.assertAll
import org.junit.jupiter.api.Assertions.assertEquals
import org.junit.jupiter.api.Test
import org.opendc.simulator.core.runBlockingSimulation
-import org.opendc.simulator.resources.SimResourceConsumer
-import org.opendc.simulator.resources.SimResourceEvent
-import org.opendc.simulator.resources.SimResourceInterpreter
-import org.opendc.simulator.resources.consumer.SimWorkConsumer
+import org.opendc.simulator.flow.FlowEngine
+import org.opendc.simulator.flow.FlowSource
+import org.opendc.simulator.flow.source.FixedFlowSource
/**
* Test suite for the [SimUps] class.
@@ -39,9 +38,9 @@ import org.opendc.simulator.resources.consumer.SimWorkConsumer
internal class SimUpsTest {
@Test
fun testSingleInlet() = runBlockingSimulation {
- val interpreter = SimResourceInterpreter(coroutineContext, clock)
- val source = SimPowerSource(interpreter, capacity = 100.0)
- val ups = SimUps(interpreter)
+ val engine = FlowEngine(coroutineContext, clock)
+ val source = SimPowerSource(engine, capacity = 100.0)
+ val ups = SimUps(engine)
source.connect(ups.newInlet())
ups.connect(SimpleInlet())
@@ -50,10 +49,10 @@ internal class SimUpsTest {
@Test
fun testDoubleInlet() = runBlockingSimulation {
- val interpreter = SimResourceInterpreter(coroutineContext, clock)
- val source1 = SimPowerSource(interpreter, capacity = 100.0)
- val source2 = SimPowerSource(interpreter, capacity = 100.0)
- val ups = SimUps(interpreter)
+ val engine = FlowEngine(coroutineContext, clock)
+ val source1 = SimPowerSource(engine, capacity = 100.0)
+ val source2 = SimPowerSource(engine, capacity = 100.0)
+ val ups = SimUps(engine)
source1.connect(ups.newInlet())
source2.connect(ups.newInlet())
@@ -67,10 +66,10 @@ internal class SimUpsTest {
@Test
fun testLoss() = runBlockingSimulation {
- val interpreter = SimResourceInterpreter(coroutineContext, clock)
- val source = SimPowerSource(interpreter, capacity = 100.0)
+ val engine = FlowEngine(coroutineContext, clock)
+ val source = SimPowerSource(engine, capacity = 100.0)
// https://download.schneider-electric.com/files?p_Doc_Ref=SPD_NRAN-66CK3D_EN
- val ups = SimUps(interpreter, idlePower = 4.0, lossCoefficient = 0.05)
+ val ups = SimUps(engine, idlePower = 4.0, lossCoefficient = 0.05)
source.connect(ups.newInlet())
ups.connect(SimpleInlet())
@@ -79,24 +78,24 @@ internal class SimUpsTest {
@Test
fun testDisconnect() = runBlockingSimulation {
- val interpreter = SimResourceInterpreter(coroutineContext, clock)
- val source1 = SimPowerSource(interpreter, capacity = 100.0)
- val source2 = SimPowerSource(interpreter, capacity = 100.0)
- val ups = SimUps(interpreter)
+ val engine = FlowEngine(coroutineContext, clock)
+ val source1 = SimPowerSource(engine, capacity = 100.0)
+ val source2 = SimPowerSource(engine, capacity = 100.0)
+ val ups = SimUps(engine)
source1.connect(ups.newInlet())
source2.connect(ups.newInlet())
- val consumer = spyk(SimWorkConsumer(100.0, utilization = 1.0))
+ val consumer = spyk(FixedFlowSource(100.0, utilization = 1.0))
val inlet = object : SimPowerInlet() {
- override fun createConsumer(): SimResourceConsumer = consumer
+ override fun createSource(): FlowSource = consumer
}
ups.connect(inlet)
ups.disconnect()
- verify { consumer.onEvent(any(), SimResourceEvent.Exit) }
+ verify { consumer.onStop(any(), any(), any()) }
}
class SimpleInlet : SimPowerInlet() {
- override fun createConsumer(): SimResourceConsumer = SimWorkConsumer(100.0, utilization = 0.5)
+ override fun createSource(): FlowSource = FixedFlowSource(100.0, utilization = 0.5)
}
}
diff --git a/opendc-simulator/opendc-simulator-resources/src/jmh/kotlin/org/opendc/simulator/resources/SimResourceBenchmarks.kt b/opendc-simulator/opendc-simulator-resources/src/jmh/kotlin/org/opendc/simulator/resources/SimResourceBenchmarks.kt
deleted file mode 100644
index b45b2a2f..00000000
--- a/opendc-simulator/opendc-simulator-resources/src/jmh/kotlin/org/opendc/simulator/resources/SimResourceBenchmarks.kt
+++ /dev/null
@@ -1,144 +0,0 @@
-/*
- * Copyright (c) 2021 AtLarge Research
- *
- * Permission is hereby granted, free of charge, to any person obtaining a copy
- * of this software and associated documentation files (the "Software"), to deal
- * in the Software without restriction, including without limitation the rights
- * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
- * copies of the Software, and to permit persons to whom the Software is
- * furnished to do so, subject to the following conditions:
- *
- * The above copyright notice and this permission notice shall be included in all
- * copies or substantial portions of the Software.
- *
- * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
- * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
- * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
- * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
- * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
- * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
- * SOFTWARE.
- */
-
-package org.opendc.simulator.resources
-
-import kotlinx.coroutines.ExperimentalCoroutinesApi
-import kotlinx.coroutines.launch
-import org.opendc.simulator.core.SimulationCoroutineScope
-import org.opendc.simulator.core.runBlockingSimulation
-import org.opendc.simulator.resources.consumer.SimTraceConsumer
-import org.openjdk.jmh.annotations.*
-import java.util.concurrent.TimeUnit
-
-@State(Scope.Thread)
-@Fork(1)
-@Warmup(iterations = 2, time = 1, timeUnit = TimeUnit.SECONDS)
-@Measurement(iterations = 5, time = 3, timeUnit = TimeUnit.SECONDS)
-@OptIn(ExperimentalCoroutinesApi::class)
-class SimResourceBenchmarks {
- private lateinit var scope: SimulationCoroutineScope
- private lateinit var interpreter: SimResourceInterpreter
-
- @Setup
- fun setUp() {
- scope = SimulationCoroutineScope()
- interpreter = SimResourceInterpreter(scope.coroutineContext, scope.clock)
- }
-
- @State(Scope.Thread)
- class Workload {
- lateinit var trace: Sequence<SimTraceConsumer.Fragment>
-
- @Setup
- fun setUp() {
- trace = sequenceOf(
- SimTraceConsumer.Fragment(1000, 28.0),
- SimTraceConsumer.Fragment(1000, 3500.0),
- SimTraceConsumer.Fragment(1000, 0.0),
- SimTraceConsumer.Fragment(1000, 183.0),
- SimTraceConsumer.Fragment(1000, 400.0),
- SimTraceConsumer.Fragment(1000, 100.0),
- SimTraceConsumer.Fragment(1000, 3000.0),
- SimTraceConsumer.Fragment(1000, 4500.0),
- )
- }
- }
-
- @Benchmark
- fun benchmarkSource(state: Workload) {
- return scope.runBlockingSimulation {
- val provider = SimResourceSource(4200.0, interpreter)
- return@runBlockingSimulation provider.consume(SimTraceConsumer(state.trace))
- }
- }
-
- @Benchmark
- fun benchmarkForwardOverhead(state: Workload) {
- return scope.runBlockingSimulation {
- val provider = SimResourceSource(4200.0, interpreter)
- val forwarder = SimResourceForwarder()
- provider.startConsumer(forwarder)
- return@runBlockingSimulation forwarder.consume(SimTraceConsumer(state.trace))
- }
- }
-
- @Benchmark
- fun benchmarkSwitchMaxMinSingleConsumer(state: Workload) {
- return scope.runBlockingSimulation {
- val switch = SimResourceSwitchMaxMin(interpreter)
-
- switch.addInput(SimResourceSource(3000.0, interpreter))
- switch.addInput(SimResourceSource(3000.0, interpreter))
-
- val provider = switch.newOutput()
- return@runBlockingSimulation provider.consume(SimTraceConsumer(state.trace))
- }
- }
-
- @Benchmark
- fun benchmarkSwitchMaxMinTripleConsumer(state: Workload) {
- return scope.runBlockingSimulation {
- val switch = SimResourceSwitchMaxMin(interpreter)
-
- switch.addInput(SimResourceSource(3000.0, interpreter))
- switch.addInput(SimResourceSource(3000.0, interpreter))
-
- repeat(3) {
- launch {
- val provider = switch.newOutput()
- provider.consume(SimTraceConsumer(state.trace))
- }
- }
- }
- }
-
- @Benchmark
- fun benchmarkSwitchExclusiveSingleConsumer(state: Workload) {
- return scope.runBlockingSimulation {
- val switch = SimResourceSwitchExclusive()
-
- switch.addInput(SimResourceSource(3000.0, interpreter))
- switch.addInput(SimResourceSource(3000.0, interpreter))
-
- val provider = switch.newOutput()
- return@runBlockingSimulation provider.consume(SimTraceConsumer(state.trace))
- }
- }
-
- @Benchmark
- fun benchmarkSwitchExclusiveTripleConsumer(state: Workload) {
- return scope.runBlockingSimulation {
- val switch = SimResourceSwitchExclusive()
-
- switch.addInput(SimResourceSource(3000.0, interpreter))
- switch.addInput(SimResourceSource(3000.0, interpreter))
-
- repeat(2) {
- launch {
- val provider = switch.newOutput()
- provider.consume(SimTraceConsumer(state.trace))
- }
- }
- }
- }
-}
diff --git a/opendc-simulator/opendc-simulator-resources/src/main/kotlin/org/opendc/simulator/resources/SimAbstractResourceAggregator.kt b/opendc-simulator/opendc-simulator-resources/src/main/kotlin/org/opendc/simulator/resources/SimAbstractResourceAggregator.kt
deleted file mode 100644
index 00648876..00000000
--- a/opendc-simulator/opendc-simulator-resources/src/main/kotlin/org/opendc/simulator/resources/SimAbstractResourceAggregator.kt
+++ /dev/null
@@ -1,211 +0,0 @@
-/*
- * Copyright (c) 2021 AtLarge Research
- *
- * Permission is hereby granted, free of charge, to any person obtaining a copy
- * of this software and associated documentation files (the "Software"), to deal
- * in the Software without restriction, including without limitation the rights
- * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
- * copies of the Software, and to permit persons to whom the Software is
- * furnished to do so, subject to the following conditions:
- *
- * The above copyright notice and this permission notice shall be included in all
- * copies or substantial portions of the Software.
- *
- * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
- * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
- * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
- * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
- * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
- * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
- * SOFTWARE.
- */
-
-package org.opendc.simulator.resources
-
-/**
- * Abstract implementation of [SimResourceAggregator].
- */
-public abstract class SimAbstractResourceAggregator(
- interpreter: SimResourceInterpreter,
- parent: SimResourceSystem?
-) : SimResourceAggregator {
- /**
- * This method is invoked when the resource consumer consumes resources.
- */
- protected abstract fun doConsume(work: Double, limit: Double, deadline: Long)
-
- /**
- * This method is invoked when the resource consumer enters an idle state.
- */
- protected abstract fun doIdle(deadline: Long)
-
- /**
- * This method is invoked when the resource consumer finishes processing.
- */
- protected abstract fun doFinish()
-
- /**
- * This method is invoked when an input context is started.
- */
- protected abstract fun onInputStarted(input: Input)
-
- /**
- * This method is invoked when an input is stopped.
- */
- protected abstract fun onInputFinished(input: Input)
-
- /* SimResourceAggregator */
- override fun addInput(input: SimResourceProvider) {
- val consumer = Consumer()
- _inputs.add(input)
- _inputConsumers.add(consumer)
- input.startConsumer(consumer)
- }
-
- override val inputs: Set<SimResourceProvider>
- get() = _inputs
- private val _inputs = mutableSetOf<SimResourceProvider>()
- private val _inputConsumers = mutableListOf<Consumer>()
-
- /* SimResourceProvider */
- override val isActive: Boolean
- get() = _output.isActive
-
- override val capacity: Double
- get() = _output.capacity
-
- override val speed: Double
- get() = _output.speed
-
- override val demand: Double
- get() = _output.demand
-
- override val counters: SimResourceCounters
- get() = _output.counters
-
- override fun startConsumer(consumer: SimResourceConsumer) {
- _output.startConsumer(consumer)
- }
-
- override fun cancel() {
- _output.cancel()
- }
-
- override fun interrupt() {
- _output.interrupt()
- }
-
- private val _output = object : SimAbstractResourceProvider(interpreter, parent, initialCapacity = 0.0) {
- override fun createLogic(): SimResourceProviderLogic {
- return object : SimResourceProviderLogic {
- override fun onIdle(ctx: SimResourceControllableContext, deadline: Long): Long {
- doIdle(deadline)
- return Long.MAX_VALUE
- }
-
- override fun onConsume(ctx: SimResourceControllableContext, work: Double, limit: Double, deadline: Long): Long {
- doConsume(work, limit, deadline)
- return Long.MAX_VALUE
- }
-
- override fun onFinish(ctx: SimResourceControllableContext) {
- doFinish()
- }
-
- override fun onUpdate(ctx: SimResourceControllableContext, work: Double, willOvercommit: Boolean) {
- updateCounters(ctx, work, willOvercommit)
- }
-
- override fun getConsumedWork(ctx: SimResourceControllableContext, work: Double, speed: Double, duration: Long): Double {
- return work - _inputConsumers.sumOf { it.remainingWork }
- }
- }
- }
-
- /**
- * Flush the progress of the output if possible.
- */
- fun flush() {
- ctx?.flush()
- }
- }
-
- /**
- * An input for the resource aggregator.
- */
- public interface Input {
- /**
- * The [SimResourceContext] associated with the input.
- */
- public val ctx: SimResourceContext
-
- /**
- * Push the specified [SimResourceCommand] to the input.
- */
- public fun push(command: SimResourceCommand)
- }
-
- /**
- * An internal [SimResourceConsumer] implementation for aggregator inputs.
- */
- private inner class Consumer : Input, SimResourceConsumer {
- /**
- * The resource context associated with the input.
- */
- override val ctx: SimResourceContext
- get() = _ctx!!
- private var _ctx: SimResourceContext? = null
-
- /**
- * The remaining work of the consumer.
- */
- val remainingWork: Double
- get() = _ctx?.remainingWork ?: 0.0
-
- /**
- * The resource command to run next.
- */
- private var command: SimResourceCommand? = null
-
- private fun updateCapacity() {
- // Adjust capacity of output resource
- _output.capacity = _inputConsumers.sumOf { it._ctx?.capacity ?: 0.0 }
- }
-
- /* Input */
- override fun push(command: SimResourceCommand) {
- this.command = command
- _ctx?.interrupt()
- }
-
- /* SimResourceConsumer */
- override fun onNext(ctx: SimResourceContext): SimResourceCommand {
- var next = command
-
- return if (next != null) {
- this.command = null
- next
- } else {
- _output.flush()
-
- next = command
- this.command = null
- next ?: SimResourceCommand.Idle()
- }
- }
-
- override fun onEvent(ctx: SimResourceContext, event: SimResourceEvent) {
- when (event) {
- SimResourceEvent.Start -> {
- _ctx = ctx
- updateCapacity()
-
- onInputStarted(this)
- }
- SimResourceEvent.Capacity -> updateCapacity()
- SimResourceEvent.Exit -> onInputFinished(this)
- else -> {}
- }
- }
- }
-}
diff --git a/opendc-simulator/opendc-simulator-resources/src/main/kotlin/org/opendc/simulator/resources/SimAbstractResourceProvider.kt b/opendc-simulator/opendc-simulator-resources/src/main/kotlin/org/opendc/simulator/resources/SimAbstractResourceProvider.kt
deleted file mode 100644
index 4e8e803a..00000000
--- a/opendc-simulator/opendc-simulator-resources/src/main/kotlin/org/opendc/simulator/resources/SimAbstractResourceProvider.kt
+++ /dev/null
@@ -1,128 +0,0 @@
-/*
- * Copyright (c) 2021 AtLarge Research
- *
- * Permission is hereby granted, free of charge, to any person obtaining a copy
- * of this software and associated documentation files (the "Software"), to deal
- * in the Software without restriction, including without limitation the rights
- * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
- * copies of the Software, and to permit persons to whom the Software is
- * furnished to do so, subject to the following conditions:
- *
- * The above copyright notice and this permission notice shall be included in all
- * copies or substantial portions of the Software.
- *
- * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
- * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
- * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
- * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
- * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
- * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
- * SOFTWARE.
- */
-
-package org.opendc.simulator.resources
-
-import org.opendc.simulator.resources.impl.SimResourceCountersImpl
-
-/**
- * Abstract implementation of the [SimResourceProvider] which can be re-used by other implementations.
- */
-public abstract class SimAbstractResourceProvider(
- private val interpreter: SimResourceInterpreter,
- private val parent: SimResourceSystem?,
- initialCapacity: Double
-) : SimResourceProvider {
- /**
- * A flag to indicate that the resource provider is active.
- */
- public override val isActive: Boolean
- get() = ctx != null
-
- /**
- * The capacity of the resource.
- */
- public override var capacity: Double = initialCapacity
- set(value) {
- field = value
- ctx?.capacity = value
- }
-
- /**
- * The current processing speed of the resource.
- */
- public override val speed: Double
- get() = ctx?.speed ?: 0.0
-
- /**
- * The resource processing speed demand at this instant.
- */
- public override val demand: Double
- get() = ctx?.demand ?: 0.0
-
- /**
- * The resource counters to track the execution metrics of the resource.
- */
- public override val counters: SimResourceCounters
- get() = _counters
- private val _counters = SimResourceCountersImpl()
-
- /**
- * The [SimResourceControllableContext] that is currently running.
- */
- protected var ctx: SimResourceControllableContext? = null
- private set
-
- /**
- * Construct the [SimResourceProviderLogic] instance for a new consumer.
- */
- protected abstract fun createLogic(): SimResourceProviderLogic
-
- /**
- * Start the specified [SimResourceControllableContext].
- */
- protected open fun start(ctx: SimResourceControllableContext) {
- ctx.start()
- }
-
- /**
- * Update the counters of the resource provider.
- */
- protected fun updateCounters(ctx: SimResourceContext, work: Double, willOvercommit: Boolean) {
- if (work <= 0.0) {
- return
- }
-
- val counters = _counters
- val remainingWork = ctx.remainingWork
- counters.demand += work
- counters.actual += work - remainingWork
-
- if (willOvercommit && remainingWork > 0.0) {
- counters.overcommit += remainingWork
- }
- }
-
- final override fun startConsumer(consumer: SimResourceConsumer) {
- check(ctx == null) { "Resource is in invalid state" }
- val ctx = interpreter.newContext(consumer, createLogic(), parent)
-
- ctx.capacity = capacity
- this.ctx = ctx
-
- start(ctx)
- }
-
- final override fun interrupt() {
- ctx?.interrupt()
- }
-
- final override fun cancel() {
- val ctx = ctx
- if (ctx != null) {
- this.ctx = null
- ctx.close()
- }
- }
-
- override fun toString(): String = "SimAbstractResourceProvider[capacity=$capacity]"
-}
diff --git a/opendc-simulator/opendc-simulator-resources/src/main/kotlin/org/opendc/simulator/resources/SimResourceAggregatorMaxMin.kt b/opendc-simulator/opendc-simulator-resources/src/main/kotlin/org/opendc/simulator/resources/SimResourceAggregatorMaxMin.kt
deleted file mode 100644
index 991cda7a..00000000
--- a/opendc-simulator/opendc-simulator-resources/src/main/kotlin/org/opendc/simulator/resources/SimResourceAggregatorMaxMin.kt
+++ /dev/null
@@ -1,74 +0,0 @@
-/*
- * Copyright (c) 2021 AtLarge Research
- *
- * Permission is hereby granted, free of charge, to any person obtaining a copy
- * of this software and associated documentation files (the "Software"), to deal
- * in the Software without restriction, including without limitation the rights
- * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
- * copies of the Software, and to permit persons to whom the Software is
- * furnished to do so, subject to the following conditions:
- *
- * The above copyright notice and this permission notice shall be included in all
- * copies or substantial portions of the Software.
- *
- * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
- * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
- * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
- * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
- * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
- * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
- * SOFTWARE.
- */
-
-package org.opendc.simulator.resources
-
-/**
- * A [SimResourceAggregator] that distributes the load equally across the input resources.
- */
-public class SimResourceAggregatorMaxMin(
- interpreter: SimResourceInterpreter,
- parent: SimResourceSystem? = null
-) : SimAbstractResourceAggregator(interpreter, parent) {
- private val consumers = mutableListOf<Input>()
-
- override fun doConsume(work: Double, limit: Double, deadline: Long) {
- // Sort all consumers by their capacity
- consumers.sortWith(compareBy { it.ctx.capacity })
-
- // Divide the requests over the available capacity of the input resources fairly
- for (input in consumers) {
- val inputCapacity = input.ctx.capacity
- val fraction = inputCapacity / capacity
- val grantedSpeed = limit * fraction
- val grantedWork = fraction * work
-
- val command = if (grantedWork > 0.0 && grantedSpeed > 0.0)
- SimResourceCommand.Consume(grantedWork, grantedSpeed, deadline)
- else
- SimResourceCommand.Idle()
- input.push(command)
- }
- }
-
- override fun doIdle(deadline: Long) {
- for (input in consumers) {
- input.push(SimResourceCommand.Idle(deadline))
- }
- }
-
- override fun doFinish() {
- val iterator = consumers.iterator()
- for (input in iterator) {
- iterator.remove()
- input.push(SimResourceCommand.Exit)
- }
- }
-
- override fun onInputStarted(input: Input) {
- consumers.add(input)
- }
-
- override fun onInputFinished(input: Input) {
- consumers.remove(input)
- }
-}
diff --git a/opendc-simulator/opendc-simulator-resources/src/main/kotlin/org/opendc/simulator/resources/SimResourceCloseableProvider.kt b/opendc-simulator/opendc-simulator-resources/src/main/kotlin/org/opendc/simulator/resources/SimResourceCloseableProvider.kt
deleted file mode 100644
index bce8274b..00000000
--- a/opendc-simulator/opendc-simulator-resources/src/main/kotlin/org/opendc/simulator/resources/SimResourceCloseableProvider.kt
+++ /dev/null
@@ -1,37 +0,0 @@
-/*
- * Copyright (c) 2021 AtLarge Research
- *
- * Permission is hereby granted, free of charge, to any person obtaining a copy
- * of this software and associated documentation files (the "Software"), to deal
- * in the Software without restriction, including without limitation the rights
- * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
- * copies of the Software, and to permit persons to whom the Software is
- * furnished to do so, subject to the following conditions:
- *
- * The above copyright notice and this permission notice shall be included in all
- * copies or substantial portions of the Software.
- *
- * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
- * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
- * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
- * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
- * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
- * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
- * SOFTWARE.
- */
-
-package org.opendc.simulator.resources
-
-/**
- * A [SimResourceProvider] that has a controllable and limited lifetime.
- *
- * This interface is used to signal that the resource provider may be closed and not reused after that point.
- */
-public interface SimResourceCloseableProvider : SimResourceProvider, AutoCloseable {
- /**
- * End the lifetime of the resource provider.
- *
- * This operation cancels the existing resource consumer and prevents the resource provider from being reused.
- */
- public override fun close()
-}
diff --git a/opendc-simulator/opendc-simulator-resources/src/main/kotlin/org/opendc/simulator/resources/SimResourceCommand.kt b/opendc-simulator/opendc-simulator-resources/src/main/kotlin/org/opendc/simulator/resources/SimResourceCommand.kt
deleted file mode 100644
index f7f3fa4d..00000000
--- a/opendc-simulator/opendc-simulator-resources/src/main/kotlin/org/opendc/simulator/resources/SimResourceCommand.kt
+++ /dev/null
@@ -1,52 +0,0 @@
-/*
- * Copyright (c) 2021 AtLarge Research
- *
- * Permission is hereby granted, free of charge, to any person obtaining a copy
- * of this software and associated documentation files (the "Software"), to deal
- * in the Software without restriction, including without limitation the rights
- * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
- * copies of the Software, and to permit persons to whom the Software is
- * furnished to do so, subject to the following conditions:
- *
- * The above copyright notice and this permission notice shall be included in all
- * copies or substantial portions of the Software.
- *
- * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
- * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
- * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
- * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
- * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
- * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
- * SOFTWARE.
- */
-
-package org.opendc.simulator.resources
-
-/**
- * A SimResourceCommand communicates to a resource how it is consumed by a [SimResourceConsumer].
- */
-public sealed class SimResourceCommand {
- /**
- * A request to the resource to perform the specified amount of work before the given [deadline].
- *
- * @param work The amount of work to process.
- * @param limit The maximum amount of work to be processed per second.
- * @param deadline The instant at which the work needs to be fulfilled.
- */
- public data class Consume(val work: Double, val limit: Double, val deadline: Long = Long.MAX_VALUE) : SimResourceCommand() {
- init {
- require(work > 0) { "Amount of work must be positive" }
- require(limit > 0) { "Limit must be positive" }
- }
- }
-
- /**
- * An indication to the resource that the consumer will idle until the specified [deadline] or if it is interrupted.
- */
- public data class Idle(val deadline: Long = Long.MAX_VALUE) : SimResourceCommand()
-
- /**
- * An indication to the resource that the consumer has finished.
- */
- public object Exit : SimResourceCommand()
-}
diff --git a/opendc-simulator/opendc-simulator-resources/src/main/kotlin/org/opendc/simulator/resources/SimResourceConsumer.kt b/opendc-simulator/opendc-simulator-resources/src/main/kotlin/org/opendc/simulator/resources/SimResourceConsumer.kt
deleted file mode 100644
index 4d937514..00000000
--- a/opendc-simulator/opendc-simulator-resources/src/main/kotlin/org/opendc/simulator/resources/SimResourceConsumer.kt
+++ /dev/null
@@ -1,56 +0,0 @@
-/*
- * Copyright (c) 2021 AtLarge Research
- *
- * Permission is hereby granted, free of charge, to any person obtaining a copy
- * of this software and associated documentation files (the "Software"), to deal
- * in the Software without restriction, including without limitation the rights
- * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
- * copies of the Software, and to permit persons to whom the Software is
- * furnished to do so, subject to the following conditions:
- *
- * The above copyright notice and this permission notice shall be included in all
- * copies or substantial portions of the Software.
- *
- * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
- * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
- * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
- * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
- * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
- * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
- * SOFTWARE.
- */
-
-package org.opendc.simulator.resources
-
-/**
- * A [SimResourceConsumer] characterizes how a resource is consumed.
- *
- * Implementors of this interface should be considered stateful and must be assumed not to be re-usable (concurrently)
- * for multiple resource providers, unless explicitly said otherwise.
- */
-public interface SimResourceConsumer {
- /**
- * This method is invoked when a resource asks for the next [command][SimResourceCommand] to process, either because
- * the resource finished processing, reached its deadline or was interrupted.
- *
- * @param ctx The execution context in which the consumer runs.
- * @return The next command that the resource should execute.
- */
- public fun onNext(ctx: SimResourceContext): SimResourceCommand
-
- /**
- * This method is invoked when an event has occurred.
- *
- * @param ctx The execution context in which the consumer runs.
- * @param event The event that has occurred.
- */
- public fun onEvent(ctx: SimResourceContext, event: SimResourceEvent) {}
-
- /**
- * This method is invoked when a resource consumer throws an exception.
- *
- * @param ctx The execution context in which the consumer runs.
- * @param cause The cause of the failure.
- */
- public fun onFailure(ctx: SimResourceContext, cause: Throwable) {}
-}
diff --git a/opendc-simulator/opendc-simulator-resources/src/main/kotlin/org/opendc/simulator/resources/SimResourceControllableContext.kt b/opendc-simulator/opendc-simulator-resources/src/main/kotlin/org/opendc/simulator/resources/SimResourceControllableContext.kt
deleted file mode 100644
index ceaca39a..00000000
--- a/opendc-simulator/opendc-simulator-resources/src/main/kotlin/org/opendc/simulator/resources/SimResourceControllableContext.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.resources
-
-/**
- * A controllable [SimResourceContext].
- *
- * This interface is used by resource providers to control the resource context.
- */
-public interface SimResourceControllableContext : SimResourceContext, AutoCloseable {
- /**
- * The state of the resource context.
- */
- public val state: SimResourceState
-
- /**
- * The capacity of the resource.
- */
- public override var capacity: Double
-
- /**
- * Start the resource context.
- */
- public fun start()
-
- /**
- * Stop the resource context.
- */
- public override fun close()
-
- /**
- * Invalidate the resource context's state.
- *
- * By invalidating the resource context's current state, the state is re-computed and the current progress is
- * materialized during the next interpreter cycle. As a result, this call run asynchronously. See [flush] for the
- * synchronous variant.
- */
- public fun invalidate()
-
- /**
- * Synchronously flush the progress of the resource context and materialize its current progress.
- */
- public fun flush()
-}
diff --git a/opendc-simulator/opendc-simulator-resources/src/main/kotlin/org/opendc/simulator/resources/SimResourceDistributorMaxMin.kt b/opendc-simulator/opendc-simulator-resources/src/main/kotlin/org/opendc/simulator/resources/SimResourceDistributorMaxMin.kt
deleted file mode 100644
index 6c1e134b..00000000
--- a/opendc-simulator/opendc-simulator-resources/src/main/kotlin/org/opendc/simulator/resources/SimResourceDistributorMaxMin.kt
+++ /dev/null
@@ -1,373 +0,0 @@
-/*
- * Copyright (c) 2021 AtLarge Research
- *
- * Permission is hereby granted, free of charge, to any person obtaining a copy
- * of this software and associated documentation files (the "Software"), to deal
- * in the Software without restriction, including without limitation the rights
- * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
- * copies of the Software, and to permit persons to whom the Software is
- * furnished to do so, subject to the following conditions:
- *
- * The above copyright notice and this permission notice shall be included in all
- * copies or substantial portions of the Software.
- *
- * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
- * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
- * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
- * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
- * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
- * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
- * SOFTWARE.
- */
-
-package org.opendc.simulator.resources
-
-import org.opendc.simulator.resources.interference.InterferenceDomain
-import org.opendc.simulator.resources.interference.InterferenceKey
-import kotlin.math.max
-import kotlin.math.min
-
-/**
- * A [SimResourceDistributor] that distributes the capacity of a resource over consumers using max-min fair sharing.
- *
- * @param interpreter The interpreter for managing the resource contexts.
- * @param parent The parent resource system of the distributor.
- * @param interferenceDomain The interference domain of the distributor.
- */
-public class SimResourceDistributorMaxMin(
- private val interpreter: SimResourceInterpreter,
- private val parent: SimResourceSystem? = null,
- private val interferenceDomain: InterferenceDomain? = null
-) : SimResourceDistributor {
- override val outputs: Set<SimResourceCloseableProvider>
- get() = _outputs
- private val _outputs = mutableSetOf<Output>()
-
- /**
- * The resource context of the consumer.
- */
- private var ctx: SimResourceContext? = null
-
- /**
- * The active outputs.
- */
- private val activeOutputs: MutableList<Output> = mutableListOf()
-
- /**
- * The total amount of work allocated to be executed.
- */
- private var totalAllocatedWork = 0.0
-
- /**
- * The total allocated speed for the output resources.
- */
- private var totalAllocatedSpeed = 0.0
-
- /**
- * The total requested speed for the output resources.
- */
- private var totalRequestedSpeed = 0.0
-
- /**
- * The resource counters of this distributor.
- */
- public val counters: Counters
- get() = _counters
- private val _counters = object : Counters {
- override var demand: Double = 0.0
- override var actual: Double = 0.0
- override var overcommit: Double = 0.0
- override var interference: Double = 0.0
-
- override fun reset() {
- demand = 0.0
- actual = 0.0
- overcommit = 0.0
- interference = 0.0
- }
-
- override fun toString(): String = "SimResourceDistributorMaxMin.Counters[demand=$demand,actual=$actual,overcommit=$overcommit,interference=$interference]"
- }
-
- /* SimResourceDistributor */
- override fun newOutput(key: InterferenceKey?): SimResourceCloseableProvider {
- val provider = Output(ctx?.capacity ?: 0.0, key)
- _outputs.add(provider)
- return provider
- }
-
- /* SimResourceConsumer */
- override fun onNext(ctx: SimResourceContext): SimResourceCommand {
- return doNext(ctx)
- }
-
- override fun onEvent(ctx: SimResourceContext, event: SimResourceEvent) {
- when (event) {
- SimResourceEvent.Start -> {
- this.ctx = ctx
- updateCapacity(ctx)
- }
- SimResourceEvent.Exit -> {
- val iterator = _outputs.iterator()
- while (iterator.hasNext()) {
- val output = iterator.next()
-
- // Remove the output from the outputs to prevent ConcurrentModificationException when removing it
- // during the call to output.close()
- iterator.remove()
-
- output.close()
- }
- }
- SimResourceEvent.Capacity -> updateCapacity(ctx)
- else -> {}
- }
- }
-
- /**
- * Extended [SimResourceCounters] interface for the distributor.
- */
- public interface Counters : SimResourceCounters {
- /**
- * The amount of work lost due to interference.
- */
- public val interference: Double
- }
-
- /**
- * Update the counters of the distributor.
- */
- private fun updateCounters(ctx: SimResourceControllableContext, work: Double, willOvercommit: Boolean) {
- if (work <= 0.0) {
- return
- }
-
- val counters = _counters
- val remainingWork = ctx.remainingWork
-
- counters.demand += work
- counters.actual += work - remainingWork
-
- if (willOvercommit && remainingWork > 0.0) {
- counters.overcommit += remainingWork
- }
- }
-
- /**
- * Schedule the work of the outputs.
- */
- private fun doNext(ctx: SimResourceContext): SimResourceCommand {
- // If there is no work yet, mark the input as idle.
- if (activeOutputs.isEmpty()) {
- return SimResourceCommand.Idle()
- }
-
- val capacity = ctx.capacity
- var duration: Double = Double.MAX_VALUE
- var deadline: Long = Long.MAX_VALUE
- var availableSpeed = capacity
- var totalRequestedSpeed = 0.0
-
- // Pull in the work of the outputs
- val outputIterator = activeOutputs.listIterator()
- for (output in outputIterator) {
- output.pull()
-
- // Remove outputs that have finished
- if (!output.isActive) {
- outputIterator.remove()
- }
- }
-
- // Sort in-place the outputs based on their requested usage.
- // Profiling shows that it is faster than maintaining some kind of sorted set.
- activeOutputs.sort()
-
- // Divide the available input capacity fairly across the outputs using max-min fair sharing
- var remaining = activeOutputs.size
- for (output in activeOutputs) {
- val availableShare = availableSpeed / remaining--
- val grantedSpeed = min(output.allowedSpeed, availableShare)
-
- deadline = min(deadline, output.deadline)
-
- // Ignore idle computation
- if (grantedSpeed <= 0.0 || output.work <= 0.0) {
- output.actualSpeed = 0.0
- continue
- }
-
- totalRequestedSpeed += output.limit
-
- output.actualSpeed = grantedSpeed
- availableSpeed -= grantedSpeed
-
- // The duration that we want to run is that of the shortest request of an output
- duration = min(duration, output.work / grantedSpeed)
- }
-
- val targetDuration = min(duration, (deadline - interpreter.clock.millis()) / 1000.0)
- var totalRequestedWork = 0.0
- var totalAllocatedWork = 0.0
- for (output in activeOutputs) {
- val work = output.work
- val speed = output.actualSpeed
- if (speed > 0.0) {
- val outputDuration = work / speed
- totalRequestedWork += work * (duration / outputDuration)
- totalAllocatedWork += work * (targetDuration / outputDuration)
- }
- }
-
- assert(deadline >= interpreter.clock.millis()) { "Deadline already passed" }
-
- this.totalRequestedSpeed = totalRequestedSpeed
- this.totalAllocatedWork = totalAllocatedWork
- val totalAllocatedSpeed = capacity - availableSpeed
- this.totalAllocatedSpeed = totalAllocatedSpeed
-
- return if (totalAllocatedWork > 0.0 && totalAllocatedSpeed > 0.0)
- SimResourceCommand.Consume(totalAllocatedWork, totalAllocatedSpeed, deadline)
- else
- SimResourceCommand.Idle(deadline)
- }
-
- private fun updateCapacity(ctx: SimResourceContext) {
- for (output in _outputs) {
- output.capacity = ctx.capacity
- }
- }
-
- /**
- * An internal [SimResourceProvider] implementation for switch outputs.
- */
- private inner class Output(capacity: Double, private val key: InterferenceKey?) :
- SimAbstractResourceProvider(interpreter, parent, capacity),
- SimResourceCloseableProvider,
- SimResourceProviderLogic,
- Comparable<Output> {
- /**
- * A flag to indicate that the output is closed.
- */
- private var isClosed: Boolean = false
-
- /**
- * The current requested work.
- */
- var work: Double = 0.0
-
- /**
- * The requested limit.
- */
- var limit: Double = 0.0
-
- /**
- * The current deadline.
- */
- var deadline: Long = Long.MAX_VALUE
-
- /**
- * The processing speed that is allowed by the model constraints.
- */
- var allowedSpeed: Double = 0.0
-
- /**
- * The actual processing speed.
- */
- var actualSpeed: Double = 0.0
-
- /**
- * The timestamp at which we received the last command.
- */
- private var lastCommandTimestamp: Long = Long.MIN_VALUE
-
- /* SimAbstractResourceProvider */
- override fun createLogic(): SimResourceProviderLogic = this
-
- override fun start(ctx: SimResourceControllableContext) {
- check(!isClosed) { "Cannot re-use closed output" }
-
- activeOutputs += this
- interpreter.batch {
- ctx.start()
- // Interrupt the input to re-schedule the resources
- this@SimResourceDistributorMaxMin.ctx?.interrupt()
- }
- }
-
- override fun close() {
- isClosed = true
- cancel()
- _outputs.remove(this)
- }
-
- /* SimResourceProviderLogic */
- override fun onIdle(ctx: SimResourceControllableContext, deadline: Long): Long {
- allowedSpeed = 0.0
- this.deadline = deadline
- work = 0.0
- limit = 0.0
- lastCommandTimestamp = ctx.clock.millis()
-
- return Long.MAX_VALUE
- }
-
- override fun onConsume(ctx: SimResourceControllableContext, work: Double, limit: Double, deadline: Long): Long {
- allowedSpeed = min(ctx.capacity, limit)
- this.work = work
- this.limit = limit
- this.deadline = deadline
- lastCommandTimestamp = ctx.clock.millis()
-
- return Long.MAX_VALUE
- }
-
- override fun onUpdate(ctx: SimResourceControllableContext, work: Double, willOvercommit: Boolean) {
- updateCounters(ctx, work, willOvercommit)
-
- this@SimResourceDistributorMaxMin.updateCounters(ctx, work, willOvercommit)
- }
-
- override fun onFinish(ctx: SimResourceControllableContext) {
- work = 0.0
- limit = 0.0
- deadline = Long.MAX_VALUE
- lastCommandTimestamp = ctx.clock.millis()
- }
-
- override fun getConsumedWork(ctx: SimResourceControllableContext, work: Double, speed: Double, duration: Long): Double {
- val totalRemainingWork = this@SimResourceDistributorMaxMin.ctx?.remainingWork ?: 0.0
-
- // Compute the fraction of compute time allocated to the output
- val fraction = actualSpeed / totalAllocatedSpeed
-
- // Compute the performance penalty due to resource interference
- val perfScore = if (interferenceDomain != null) {
- val load = totalAllocatedSpeed / requireNotNull(this@SimResourceDistributorMaxMin.ctx).capacity
- interferenceDomain.apply(key, load)
- } else {
- 1.0
- }
-
- // Compute the work that was actually granted to the output.
- val potentialConsumedWork = (totalAllocatedWork - totalRemainingWork) * fraction
-
- _counters.interference += potentialConsumedWork * max(0.0, 1 - perfScore)
-
- return potentialConsumedWork
- }
-
- /* Comparable */
- override fun compareTo(other: Output): Int = allowedSpeed.compareTo(other.allowedSpeed)
-
- /**
- * Pull the next command if necessary.
- */
- fun pull() {
- val ctx = ctx
- if (ctx != null && lastCommandTimestamp < ctx.clock.millis()) {
- ctx.flush()
- }
- }
- }
-}
diff --git a/opendc-simulator/opendc-simulator-resources/src/main/kotlin/org/opendc/simulator/resources/SimResourceFlow.kt b/opendc-simulator/opendc-simulator-resources/src/main/kotlin/org/opendc/simulator/resources/SimResourceFlow.kt
deleted file mode 100644
index bbf6ad44..00000000
--- a/opendc-simulator/opendc-simulator-resources/src/main/kotlin/org/opendc/simulator/resources/SimResourceFlow.kt
+++ /dev/null
@@ -1,29 +0,0 @@
-/*
- * Copyright (c) 2021 AtLarge Research
- *
- * Permission is hereby granted, free of charge, to any person obtaining a copy
- * of this software and associated documentation files (the "Software"), to deal
- * in the Software without restriction, including without limitation the rights
- * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
- * copies of the Software, and to permit persons to whom the Software is
- * furnished to do so, subject to the following conditions:
- *
- * The above copyright notice and this permission notice shall be included in all
- * copies or substantial portions of the Software.
- *
- * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
- * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
- * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
- * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
- * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
- * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
- * SOFTWARE.
- */
-
-package org.opendc.simulator.resources
-
-/**
- * A [SimResourceFlow] acts as both a resource consumer and resource provider at the same time, simplifying bridging
- * between different components.
- */
-public interface SimResourceFlow : SimResourceConsumer, SimResourceProvider
diff --git a/opendc-simulator/opendc-simulator-resources/src/main/kotlin/org/opendc/simulator/resources/SimResourceProvider.kt b/opendc-simulator/opendc-simulator-resources/src/main/kotlin/org/opendc/simulator/resources/SimResourceProvider.kt
deleted file mode 100644
index b68b7261..00000000
--- a/opendc-simulator/opendc-simulator-resources/src/main/kotlin/org/opendc/simulator/resources/SimResourceProvider.kt
+++ /dev/null
@@ -1,106 +0,0 @@
-/*
- * Copyright (c) 2021 AtLarge Research
- *
- * Permission is hereby granted, free of charge, to any person obtaining a copy
- * of this software and associated documentation files (the "Software"), to deal
- * in the Software without restriction, including without limitation the rights
- * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
- * copies of the Software, and to permit persons to whom the Software is
- * furnished to do so, subject to the following conditions:
- *
- * The above copyright notice and this permission notice shall be included in all
- * copies or substantial portions of the Software.
- *
- * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
- * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
- * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
- * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
- * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
- * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
- * SOFTWARE.
- */
-
-package org.opendc.simulator.resources
-
-import kotlinx.coroutines.suspendCancellableCoroutine
-import kotlin.coroutines.resume
-import kotlin.coroutines.resumeWithException
-
-/**
- * A [SimResourceProvider] provides a resource that can be consumed by a [SimResourceConsumer].
- */
-public interface SimResourceProvider {
- /**
- * A flag to indicate that the resource provider is currently being consumed by a [SimResourceConsumer].
- */
- public val isActive: Boolean
-
- /**
- * The resource capacity available at this instant.
- */
- public val capacity: Double
-
- /**
- * The current processing speed of the resource.
- */
- public val speed: Double
-
- /**
- * The resource processing speed demand at this instant.
- */
- public val demand: Double
-
- /**
- * The resource counters to track the execution metrics of the resource.
- */
- public val counters: SimResourceCounters
-
- /**
- * Start the specified [resource consumer][consumer] in the context of this resource provider asynchronously.
- *
- * @throws IllegalStateException if there is already a consumer active or the resource lifetime has ended.
- */
- public fun startConsumer(consumer: SimResourceConsumer)
-
- /**
- * Interrupt the resource consumer. If there is no consumer active, this operation will be a no-op.
- */
- public fun interrupt()
-
- /**
- * Cancel the current resource consumer. If there is no consumer active, this operation will be a no-op.
- */
- public fun cancel()
-}
-
-/**
- * Consume the resource provided by this provider using the specified [consumer] and suspend execution until
- * the consumer has finished.
- */
-public suspend fun SimResourceProvider.consume(consumer: SimResourceConsumer) {
- return suspendCancellableCoroutine { cont ->
- startConsumer(object : SimResourceConsumer by consumer {
- override fun onEvent(ctx: SimResourceContext, event: SimResourceEvent) {
- consumer.onEvent(ctx, event)
-
- if (event == SimResourceEvent.Exit && !cont.isCompleted) {
- cont.resume(Unit)
- }
- }
-
- override fun onFailure(ctx: SimResourceContext, cause: Throwable) {
- try {
- consumer.onFailure(ctx, cause)
- cont.resumeWithException(cause)
- } catch (e: Throwable) {
- e.addSuppressed(cause)
- cont.resumeWithException(e)
- }
- }
-
- override fun toString(): String = "SimSuspendingResourceConsumer"
- })
-
- cont.invokeOnCancellation { cancel() }
- }
-}
diff --git a/opendc-simulator/opendc-simulator-resources/src/main/kotlin/org/opendc/simulator/resources/SimResourceProviderLogic.kt b/opendc-simulator/opendc-simulator-resources/src/main/kotlin/org/opendc/simulator/resources/SimResourceProviderLogic.kt
deleted file mode 100644
index 2fe1b00f..00000000
--- a/opendc-simulator/opendc-simulator-resources/src/main/kotlin/org/opendc/simulator/resources/SimResourceProviderLogic.kt
+++ /dev/null
@@ -1,79 +0,0 @@
-/*
- * Copyright (c) 2021 AtLarge Research
- *
- * Permission is hereby granted, free of charge, to any person obtaining a copy
- * of this software and associated documentation files (the "Software"), to deal
- * in the Software without restriction, including without limitation the rights
- * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
- * copies of the Software, and to permit persons to whom the Software is
- * furnished to do so, subject to the following conditions:
- *
- * The above copyright notice and this permission notice shall be included in all
- * copies or substantial portions of the Software.
- *
- * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
- * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
- * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
- * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
- * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
- * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
- * SOFTWARE.
- */
-
-package org.opendc.simulator.resources
-
-/**
- * The logic of a resource provider.
- */
-public interface SimResourceProviderLogic {
- /**
- * This method is invoked when the resource is reported to idle until the specified [deadline].
- *
- * @param ctx The context in which the provider runs.
- * @param deadline The deadline that was requested by the resource consumer.
- * @return The instant at which to resume the consumer.
- */
- public fun onIdle(ctx: SimResourceControllableContext, deadline: Long): Long
-
- /**
- * This method is invoked when the resource will be consumed until the specified amount of [work] was processed
- * or [deadline] is reached.
- *
- * @param ctx The context in which the provider runs.
- * @param work The amount of work that was requested by the resource consumer.
- * @param limit The limit on the work rate of the resource consumer.
- * @param deadline The deadline that was requested by the resource consumer.
- * @return The instant at which to resume the consumer.
- */
- public fun onConsume(ctx: SimResourceControllableContext, work: Double, limit: Double, deadline: Long): Long
-
- /**
- * This method is invoked when the progress of the resource consumer is materialized.
- *
- * @param ctx The context in which the provider runs.
- * @param work The amount of work that was requested by the resource consumer.
- * @param willOvercommit A flag to indicate that the remaining work is overcommitted.
- */
- public fun onUpdate(ctx: SimResourceControllableContext, work: Double, willOvercommit: Boolean) {}
-
- /**
- * This method is invoked when the resource consumer has finished.
- */
- public fun onFinish(ctx: SimResourceControllableContext)
-
- /**
- * Compute the amount of work that was consumed over the specified [duration].
- *
- * @param work The total size of the resource consumption.
- * @param speed The speed of the resource provider.
- * @param duration The duration from the start of the consumption until now.
- * @return The amount of work that was consumed by the resource provider.
- */
- public fun getConsumedWork(ctx: SimResourceControllableContext, work: Double, speed: Double, duration: Long): Double {
- return if (duration > 0L) {
- return (duration / 1000.0) * speed
- } else {
- work
- }
- }
-}
diff --git a/opendc-simulator/opendc-simulator-resources/src/main/kotlin/org/opendc/simulator/resources/SimResourceSource.kt b/opendc-simulator/opendc-simulator-resources/src/main/kotlin/org/opendc/simulator/resources/SimResourceSource.kt
deleted file mode 100644
index 2d53198a..00000000
--- a/opendc-simulator/opendc-simulator-resources/src/main/kotlin/org/opendc/simulator/resources/SimResourceSource.kt
+++ /dev/null
@@ -1,72 +0,0 @@
-/*
- * Copyright (c) 2021 AtLarge Research
- *
- * Permission is hereby granted, free of charge, to any person obtaining a copy
- * of this software and associated documentation files (the "Software"), to deal
- * in the Software without restriction, including without limitation the rights
- * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
- * copies of the Software, and to permit persons to whom the Software is
- * furnished to do so, subject to the following conditions:
- *
- * The above copyright notice and this permission notice shall be included in all
- * copies or substantial portions of the Software.
- *
- * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
- * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
- * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
- * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
- * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
- * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
- * SOFTWARE.
- */
-
-package org.opendc.simulator.resources
-
-import kotlin.math.ceil
-import kotlin.math.min
-
-/**
- * A [SimResourceSource] represents a source for some resource that provides bounded processing capacity.
- *
- * @param initialCapacity The initial capacity of the resource.
- * @param interpreter The interpreter that is used for managing the resource contexts.
- * @param parent The parent resource system.
- */
-public class SimResourceSource(
- initialCapacity: Double,
- private val interpreter: SimResourceInterpreter,
- private val parent: SimResourceSystem? = null
-) : SimAbstractResourceProvider(interpreter, parent, initialCapacity) {
- override fun createLogic(): SimResourceProviderLogic {
- return object : SimResourceProviderLogic {
- override fun onIdle(ctx: SimResourceControllableContext, deadline: Long): Long {
- return deadline
- }
-
- override fun onConsume(ctx: SimResourceControllableContext, work: Double, limit: Double, deadline: Long): Long {
- return if (work.isInfinite()) {
- Long.MAX_VALUE
- } else {
- min(deadline, ctx.clock.millis() + getDuration(work, speed))
- }
- }
-
- override fun onUpdate(ctx: SimResourceControllableContext, work: Double, willOvercommit: Boolean) {
- updateCounters(ctx, work, willOvercommit)
- }
-
- override fun onFinish(ctx: SimResourceControllableContext) {
- cancel()
- }
- }
- }
-
- override fun toString(): String = "SimResourceSource[capacity=$capacity]"
-
- /**
- * Compute the duration that a resource consumption will take with the specified [speed].
- */
- private fun getDuration(work: Double, speed: Double): Long {
- return ceil(work / speed * 1000).toLong()
- }
-}
diff --git a/opendc-simulator/opendc-simulator-resources/src/main/kotlin/org/opendc/simulator/resources/SimResourceSwitch.kt b/opendc-simulator/opendc-simulator-resources/src/main/kotlin/org/opendc/simulator/resources/SimResourceSwitch.kt
deleted file mode 100644
index d2aab634..00000000
--- a/opendc-simulator/opendc-simulator-resources/src/main/kotlin/org/opendc/simulator/resources/SimResourceSwitch.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.resources
-
-import org.opendc.simulator.resources.interference.InterferenceKey
-
-/**
- * A [SimResourceSwitch] enables switching of capacity of multiple resources between multiple consumers.
- */
-public interface SimResourceSwitch : AutoCloseable {
- /**
- * The output resource providers to which resource consumers can be attached.
- */
- public val outputs: Set<SimResourceCloseableProvider>
-
- /**
- * The input resources that will be switched between the output providers.
- */
- public val inputs: Set<SimResourceProvider>
-
- /**
- * The resource counters to track the execution metrics of all switch resources.
- */
- public val counters: SimResourceCounters
-
- /**
- * Create a new output on the switch.
- *
- * @param key The key of the interference member to which the output belongs.
- */
- public fun newOutput(key: InterferenceKey? = null): SimResourceCloseableProvider
-
- /**
- * Add the specified [input] to the switch.
- */
- public fun addInput(input: SimResourceProvider)
-}
diff --git a/opendc-simulator/opendc-simulator-resources/src/main/kotlin/org/opendc/simulator/resources/SimResourceSwitchExclusive.kt b/opendc-simulator/opendc-simulator-resources/src/main/kotlin/org/opendc/simulator/resources/SimResourceSwitchExclusive.kt
deleted file mode 100644
index fbb541e5..00000000
--- a/opendc-simulator/opendc-simulator-resources/src/main/kotlin/org/opendc/simulator/resources/SimResourceSwitchExclusive.kt
+++ /dev/null
@@ -1,115 +0,0 @@
-/*
- * Copyright (c) 2021 AtLarge Research
- *
- * Permission is hereby granted, free of charge, to any person obtaining a copy
- * of this software and associated documentation files (the "Software"), to deal
- * in the Software without restriction, including without limitation the rights
- * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
- * copies of the Software, and to permit persons to whom the Software is
- * furnished to do so, subject to the following conditions:
- *
- * The above copyright notice and this permission notice shall be included in all
- * copies or substantial portions of the Software.
- *
- * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
- * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
- * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
- * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
- * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
- * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
- * SOFTWARE.
- */
-
-package org.opendc.simulator.resources
-
-import org.opendc.simulator.resources.interference.InterferenceKey
-import java.util.ArrayDeque
-
-/**
- * A [SimResourceSwitch] implementation that allocates outputs to the inputs of the switch exclusively. This means that
- * a single output is directly connected to an input and that the switch can only support as much outputs as inputs.
- */
-public class SimResourceSwitchExclusive : SimResourceSwitch {
- /**
- * A flag to indicate that the switch is closed.
- */
- private var isClosed: Boolean = false
-
- private val _outputs = mutableSetOf<Provider>()
- override val outputs: Set<SimResourceCloseableProvider>
- get() = _outputs
-
- private val availableResources = ArrayDeque<SimResourceTransformer>()
-
- private val _inputs = mutableSetOf<SimResourceProvider>()
- override val inputs: Set<SimResourceProvider>
- get() = _inputs
-
- override val counters: SimResourceCounters = object : SimResourceCounters {
- override val demand: Double
- get() = _inputs.sumOf { it.counters.demand }
- override val actual: Double
- get() = _inputs.sumOf { it.counters.actual }
- override val overcommit: Double
- get() = _inputs.sumOf { it.counters.overcommit }
-
- override fun reset() {
- for (input in _inputs) {
- input.counters.reset()
- }
- }
-
- override fun toString(): String = "SimResourceCounters[demand=$demand,actual=$actual,overcommit=$overcommit]"
- }
-
- /**
- * Add an output to the switch.
- */
- override fun newOutput(key: InterferenceKey?): SimResourceCloseableProvider {
- check(!isClosed) { "Switch has been closed" }
- check(availableResources.isNotEmpty()) { "No capacity to serve request" }
- val forwarder = availableResources.poll()
- val output = Provider(forwarder)
- _outputs += output
- return output
- }
-
- override fun addInput(input: SimResourceProvider) {
- check(!isClosed) { "Switch has been closed" }
-
- if (input in inputs) {
- return
- }
-
- val forwarder = SimResourceForwarder()
-
- _inputs += input
- availableResources += forwarder
-
- input.startConsumer(object : SimResourceConsumer by forwarder {
- override fun onEvent(ctx: SimResourceContext, event: SimResourceEvent) {
- if (event == SimResourceEvent.Exit) {
- // De-register the input after it has finished
- _inputs -= input
- }
-
- forwarder.onEvent(ctx, event)
- }
- })
- }
-
- override fun close() {
- isClosed = true
-
- // Cancel all upstream subscriptions
- _inputs.forEach(SimResourceProvider::cancel)
- }
-
- private inner class Provider(private val forwarder: SimResourceTransformer) : SimResourceCloseableProvider, SimResourceProvider by forwarder {
- override fun close() {
- // We explicitly do not close the forwarder here in order to re-use it across output resources.
- _outputs -= this
- availableResources += forwarder
- }
- }
-}
diff --git a/opendc-simulator/opendc-simulator-resources/src/main/kotlin/org/opendc/simulator/resources/SimResourceSwitchMaxMin.kt b/opendc-simulator/opendc-simulator-resources/src/main/kotlin/org/opendc/simulator/resources/SimResourceSwitchMaxMin.kt
deleted file mode 100644
index e368609f..00000000
--- a/opendc-simulator/opendc-simulator-resources/src/main/kotlin/org/opendc/simulator/resources/SimResourceSwitchMaxMin.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.resources
-
-import org.opendc.simulator.resources.interference.InterferenceDomain
-import org.opendc.simulator.resources.interference.InterferenceKey
-
-/**
- * A [SimResourceSwitch] implementation that switches resource consumptions over the available resources using max-min
- * fair sharing.
- *
- * @param interpreter The interpreter for managing the resource contexts.
- * @param parent The parent resource system of the switch.
- * @param interferenceDomain The interference domain of the switch.
- */
-public class SimResourceSwitchMaxMin(
- interpreter: SimResourceInterpreter,
- parent: SimResourceSystem? = null,
- interferenceDomain: InterferenceDomain? = null
-) : SimResourceSwitch {
- /**
- * The output resource providers to which resource consumers can be attached.
- */
- override val outputs: Set<SimResourceCloseableProvider>
- get() = distributor.outputs
-
- /**
- * The input resources that will be switched between the output providers.
- */
- override val inputs: Set<SimResourceProvider>
- get() = aggregator.inputs
-
- /**
- * The resource counters to track the execution metrics of all switch resources.
- */
- override val counters: SimResourceDistributorMaxMin.Counters
- get() = distributor.counters
-
- /**
- * A flag to indicate that the switch was closed.
- */
- private var isClosed = false
-
- /**
- * The aggregator to aggregate the resources.
- */
- private val aggregator = SimResourceAggregatorMaxMin(interpreter, parent)
-
- /**
- * The distributor to distribute the aggregated resources.
- */
- private val distributor = SimResourceDistributorMaxMin(interpreter, parent, interferenceDomain)
-
- init {
- aggregator.startConsumer(distributor)
- }
-
- /**
- * Add an output to the switch.
- */
- override fun newOutput(key: InterferenceKey?): SimResourceCloseableProvider {
- check(!isClosed) { "Switch has been closed" }
-
- return distributor.newOutput(key)
- }
-
- /**
- * Add the specified [input] to the switch.
- */
- override fun addInput(input: SimResourceProvider) {
- check(!isClosed) { "Switch has been closed" }
-
- aggregator.addInput(input)
- }
-
- override fun close() {
- if (!isClosed) {
- isClosed = true
- aggregator.cancel()
- }
- }
-}
diff --git a/opendc-simulator/opendc-simulator-resources/src/main/kotlin/org/opendc/simulator/resources/SimResourceSystem.kt b/opendc-simulator/opendc-simulator-resources/src/main/kotlin/org/opendc/simulator/resources/SimResourceSystem.kt
deleted file mode 100644
index 609262cb..00000000
--- a/opendc-simulator/opendc-simulator-resources/src/main/kotlin/org/opendc/simulator/resources/SimResourceSystem.kt
+++ /dev/null
@@ -1,43 +0,0 @@
-/*
- * Copyright (c) 2021 AtLarge Research
- *
- * Permission is hereby granted, free of charge, to any person obtaining a copy
- * of this software and associated documentation files (the "Software"), to deal
- * in the Software without restriction, including without limitation the rights
- * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
- * copies of the Software, and to permit persons to whom the Software is
- * furnished to do so, subject to the following conditions:
- *
- * The above copyright notice and this permission notice shall be included in all
- * copies or substantial portions of the Software.
- *
- * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
- * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
- * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
- * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
- * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
- * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
- * SOFTWARE.
- */
-
-package org.opendc.simulator.resources
-
-/**
- * A system of possible multiple sub-resources.
- *
- * This interface is used to model hierarchies of resource providers, which can listen efficiently to changes of the
- * resource provider.
- */
-public interface SimResourceSystem {
- /**
- * The parent system to which this system belongs or `null` if it has no parent.
- */
- public val parent: SimResourceSystem?
-
- /**
- * This method is invoked when the system has converged to a steady-state.
- *
- * @param timestamp The timestamp at which the system converged.
- */
- public fun onConverge(timestamp: Long)
-}
diff --git a/opendc-simulator/opendc-simulator-resources/src/main/kotlin/org/opendc/simulator/resources/SimResourceTransformer.kt b/opendc-simulator/opendc-simulator-resources/src/main/kotlin/org/opendc/simulator/resources/SimResourceTransformer.kt
deleted file mode 100644
index cec27e1c..00000000
--- a/opendc-simulator/opendc-simulator-resources/src/main/kotlin/org/opendc/simulator/resources/SimResourceTransformer.kt
+++ /dev/null
@@ -1,206 +0,0 @@
-/*
- * Copyright (c) 2021 AtLarge Research
- *
- * Permission is hereby granted, free of charge, to any person obtaining a copy
- * of this software and associated documentation files (the "Software"), to deal
- * in the Software without restriction, including without limitation the rights
- * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
- * copies of the Software, and to permit persons to whom the Software is
- * furnished to do so, subject to the following conditions:
- *
- * The above copyright notice and this permission notice shall be included in all
- * copies or substantial portions of the Software.
- *
- * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
- * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
- * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
- * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
- * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
- * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
- * SOFTWARE.
- */
-
-package org.opendc.simulator.resources
-
-import org.opendc.simulator.resources.impl.SimResourceCountersImpl
-
-/**
- * A [SimResourceFlow] that transforms the resource commands emitted by the resource commands to the resource provider.
- *
- * @param isCoupled A flag to indicate that the transformer will exit when the resource consumer exits.
- * @param transform The function to transform the received resource command.
- */
-public class SimResourceTransformer(
- private val isCoupled: Boolean = false,
- private val transform: (SimResourceContext, SimResourceCommand) -> SimResourceCommand
-) : SimResourceFlow, AutoCloseable {
- /**
- * The [SimResourceContext] in which the forwarder runs.
- */
- private var ctx: SimResourceContext? = null
-
- /**
- * The delegate [SimResourceConsumer].
- */
- private var delegate: SimResourceConsumer? = null
-
- /**
- * A flag to indicate that the delegate was started.
- */
- private var hasDelegateStarted: Boolean = false
-
- override val isActive: Boolean
- get() = delegate != null
-
- override val capacity: Double
- get() = ctx?.capacity ?: 0.0
-
- override val speed: Double
- get() = ctx?.speed ?: 0.0
-
- override val demand: Double
- get() = ctx?.demand ?: 0.0
-
- override val counters: SimResourceCounters
- get() = _counters
- private val _counters = SimResourceCountersImpl()
-
- override fun startConsumer(consumer: SimResourceConsumer) {
- check(delegate == null) { "Resource transformer already active" }
-
- delegate = consumer
-
- // Interrupt the provider to replace the consumer
- interrupt()
- }
-
- override fun interrupt() {
- ctx?.interrupt()
- }
-
- override fun cancel() {
- val delegate = delegate
- val ctx = ctx
-
- if (delegate != null) {
- this.delegate = null
-
- if (ctx != null) {
- delegate.onEvent(ctx, SimResourceEvent.Exit)
- }
- }
- }
-
- override fun close() {
- val ctx = ctx
-
- if (ctx != null) {
- this.ctx = null
- ctx.interrupt()
- }
- }
-
- override fun onNext(ctx: SimResourceContext): SimResourceCommand {
- val delegate = delegate
-
- if (!hasDelegateStarted) {
- start()
- }
-
- updateCounters(ctx)
-
- return if (delegate != null) {
- val command = transform(ctx, delegate.onNext(ctx))
-
- _work = if (command is SimResourceCommand.Consume) command.work else 0.0
-
- if (command == SimResourceCommand.Exit) {
- // Warning: resumption of the continuation might change the entire state of the forwarder. Make sure we
- // reset beforehand the existing state and check whether it has been updated afterwards
- reset()
-
- delegate.onEvent(ctx, SimResourceEvent.Exit)
-
- if (isCoupled)
- SimResourceCommand.Exit
- else
- onNext(ctx)
- } else {
- command
- }
- } else {
- SimResourceCommand.Idle()
- }
- }
-
- override fun onEvent(ctx: SimResourceContext, event: SimResourceEvent) {
- when (event) {
- SimResourceEvent.Start -> {
- this.ctx = ctx
- }
- SimResourceEvent.Exit -> {
- this.ctx = null
-
- val delegate = delegate
- if (delegate != null) {
- reset()
- delegate.onEvent(ctx, SimResourceEvent.Exit)
- }
- }
- else -> delegate?.onEvent(ctx, event)
- }
- }
-
- override fun onFailure(ctx: SimResourceContext, cause: Throwable) {
- this.ctx = null
-
- val delegate = delegate
- if (delegate != null) {
- reset()
- delegate.onFailure(ctx, cause)
- }
- }
-
- /**
- * Start the delegate.
- */
- private fun start() {
- val delegate = delegate ?: return
- delegate.onEvent(checkNotNull(ctx), SimResourceEvent.Start)
-
- hasDelegateStarted = true
- }
-
- /**
- * Reset the delegate.
- */
- private fun reset() {
- delegate = null
- hasDelegateStarted = false
- }
-
- /**
- * Counter to track the current submitted work.
- */
- private var _work = 0.0
-
- /**
- * Update the resource counters for the transformer.
- */
- private fun updateCounters(ctx: SimResourceContext) {
- val counters = _counters
- val remainingWork = ctx.remainingWork
- counters.demand += _work
- counters.actual += _work - remainingWork
- counters.overcommit += remainingWork
- }
-}
-
-/**
- * Constructs a [SimResourceTransformer] that forwards the received resource command with an identity transform.
- *
- * @param isCoupled A flag to indicate that the transformer will exit when the resource consumer exits.
- */
-public fun SimResourceForwarder(isCoupled: Boolean = false): SimResourceTransformer {
- return SimResourceTransformer(isCoupled) { _, command -> command }
-}
diff --git a/opendc-simulator/opendc-simulator-resources/src/main/kotlin/org/opendc/simulator/resources/consumer/SimSpeedConsumerAdapter.kt b/opendc-simulator/opendc-simulator-resources/src/main/kotlin/org/opendc/simulator/resources/consumer/SimSpeedConsumerAdapter.kt
deleted file mode 100644
index 4f4ebb14..00000000
--- a/opendc-simulator/opendc-simulator-resources/src/main/kotlin/org/opendc/simulator/resources/consumer/SimSpeedConsumerAdapter.kt
+++ /dev/null
@@ -1,83 +0,0 @@
-/*
- * Copyright (c) 2021 AtLarge Research
- *
- * Permission is hereby granted, free of charge, to any person obtaining a copy
- * of this software and associated documentation files (the "Software"), to deal
- * in the Software without restriction, including without limitation the rights
- * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
- * copies of the Software, and to permit persons to whom the Software is
- * furnished to do so, subject to the following conditions:
- *
- * The above copyright notice and this permission notice shall be included in all
- * copies or substantial portions of the Software.
- *
- * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
- * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
- * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
- * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
- * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
- * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
- * SOFTWARE.
- */
-
-package org.opendc.simulator.resources.consumer
-
-import org.opendc.simulator.resources.SimResourceCommand
-import org.opendc.simulator.resources.SimResourceConsumer
-import org.opendc.simulator.resources.SimResourceContext
-import org.opendc.simulator.resources.SimResourceEvent
-import kotlin.math.min
-
-/**
- * Helper class to expose an observable [speed] field describing the speed of the consumer.
- */
-public class SimSpeedConsumerAdapter(
- private val delegate: SimResourceConsumer,
- private val callback: (Double) -> Unit = {}
-) : SimResourceConsumer by delegate {
- /**
- * The resource processing speed at this instant.
- */
- public var speed: Double = 0.0
- private set(value) {
- if (field != value) {
- callback(value)
- field = value
- }
- }
-
- init {
- callback(0.0)
- }
-
- override fun onNext(ctx: SimResourceContext): SimResourceCommand {
- return delegate.onNext(ctx)
- }
-
- override fun onEvent(ctx: SimResourceContext, event: SimResourceEvent) {
- val oldSpeed = speed
-
- delegate.onEvent(ctx, event)
-
- when (event) {
- SimResourceEvent.Run -> speed = ctx.speed
- SimResourceEvent.Capacity -> {
- // Check if the consumer interrupted the consumer and updated the resource consumption. If not, we might
- // need to update the current speed.
- if (oldSpeed == speed) {
- speed = min(ctx.capacity, speed)
- }
- }
- SimResourceEvent.Exit -> speed = 0.0
- else -> {}
- }
- }
-
- override fun onFailure(ctx: SimResourceContext, cause: Throwable) {
- speed = 0.0
-
- delegate.onFailure(ctx, cause)
- }
-
- override fun toString(): String = "SimSpeedConsumerAdapter[delegate=$delegate]"
-}
diff --git a/opendc-simulator/opendc-simulator-resources/src/main/kotlin/org/opendc/simulator/resources/consumer/SimTraceConsumer.kt b/opendc-simulator/opendc-simulator-resources/src/main/kotlin/org/opendc/simulator/resources/consumer/SimTraceConsumer.kt
deleted file mode 100644
index 2e94e1c1..00000000
--- a/opendc-simulator/opendc-simulator-resources/src/main/kotlin/org/opendc/simulator/resources/consumer/SimTraceConsumer.kt
+++ /dev/null
@@ -1,73 +0,0 @@
-/*
- * Copyright (c) 2021 AtLarge Research
- *
- * Permission is hereby granted, free of charge, to any person obtaining a copy
- * of this software and associated documentation files (the "Software"), to deal
- * in the Software without restriction, including without limitation the rights
- * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
- * copies of the Software, and to permit persons to whom the Software is
- * furnished to do so, subject to the following conditions:
- *
- * The above copyright notice and this permission notice shall be included in all
- * copies or substantial portions of the Software.
- *
- * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
- * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
- * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
- * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
- * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
- * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
- * SOFTWARE.
- */
-
-package org.opendc.simulator.resources.consumer
-
-import org.opendc.simulator.resources.SimResourceCommand
-import org.opendc.simulator.resources.SimResourceConsumer
-import org.opendc.simulator.resources.SimResourceContext
-import org.opendc.simulator.resources.SimResourceEvent
-
-/**
- * A [SimResourceConsumer] that replays a workload trace consisting of multiple fragments, each indicating the resource
- * consumption for some period of time.
- */
-public class SimTraceConsumer(private val trace: Sequence<Fragment>) : SimResourceConsumer {
- private var iterator: Iterator<Fragment>? = null
-
- override fun onNext(ctx: SimResourceContext): SimResourceCommand {
- val iterator = checkNotNull(iterator)
- return if (iterator.hasNext()) {
- val now = ctx.clock.millis()
- val fragment = iterator.next()
- val work = (fragment.duration / 1000) * fragment.usage
- val deadline = now + fragment.duration
-
- assert(deadline >= now) { "Deadline already passed" }
-
- if (work > 0.0)
- SimResourceCommand.Consume(work, fragment.usage, deadline)
- else
- SimResourceCommand.Idle(deadline)
- } else {
- SimResourceCommand.Exit
- }
- }
-
- override fun onEvent(ctx: SimResourceContext, event: SimResourceEvent) {
- when (event) {
- SimResourceEvent.Start -> {
- check(iterator == null) { "Consumer already running" }
- iterator = trace.iterator()
- }
- SimResourceEvent.Exit -> {
- iterator = null
- }
- else -> {}
- }
- }
-
- /**
- * A fragment of the workload.
- */
- public data class Fragment(val duration: Long, val usage: Double)
-}
diff --git a/opendc-simulator/opendc-simulator-resources/src/main/kotlin/org/opendc/simulator/resources/impl/SimResourceContextImpl.kt b/opendc-simulator/opendc-simulator-resources/src/main/kotlin/org/opendc/simulator/resources/impl/SimResourceContextImpl.kt
deleted file mode 100644
index b79998a3..00000000
--- a/opendc-simulator/opendc-simulator-resources/src/main/kotlin/org/opendc/simulator/resources/impl/SimResourceContextImpl.kt
+++ /dev/null
@@ -1,393 +0,0 @@
-/*
- * Copyright (c) 2021 AtLarge Research
- *
- * Permission is hereby granted, free of charge, to any person obtaining a copy
- * of this software and associated documentation files (the "Software"), to deal
- * in the Software without restriction, including without limitation the rights
- * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
- * copies of the Software, and to permit persons to whom the Software is
- * furnished to do so, subject to the following conditions:
- *
- * The above copyright notice and this permission notice shall be included in all
- * copies or substantial portions of the Software.
- *
- * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
- * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
- * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
- * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
- * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
- * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
- * SOFTWARE.
- */
-
-package org.opendc.simulator.resources.impl
-
-import org.opendc.simulator.resources.*
-import java.time.Clock
-import kotlin.math.max
-import kotlin.math.min
-
-/**
- * Implementation of a [SimResourceContext] managing the communication between resources and resource consumers.
- */
-internal class SimResourceContextImpl(
- override val parent: SimResourceSystem?,
- private val interpreter: SimResourceInterpreterImpl,
- private val consumer: SimResourceConsumer,
- private val logic: SimResourceProviderLogic
-) : SimResourceControllableContext, SimResourceSystem {
- /**
- * The clock of the context.
- */
- override val clock: Clock
- get() = _clock
- private val _clock = interpreter.clock
-
- /**
- * The capacity of the resource.
- */
- override var capacity: Double = 0.0
- set(value) {
- val oldValue = field
-
- // Only changes will be propagated
- if (value != oldValue) {
- field = value
- onCapacityChange()
- }
- }
-
- /**
- * The amount of work still remaining at this instant.
- */
- override val remainingWork: Double
- get() = getRemainingWork(_clock.millis())
-
- /**
- * A flag to indicate the state of the context.
- */
- override val state: SimResourceState
- get() = _state
- private var _state = SimResourceState.Pending
-
- /**
- * The current processing speed of the resource.
- */
- override val speed: Double
- get() = _speed
- private var _speed = 0.0
-
- /**
- * The current resource processing demand.
- */
- override val demand: Double
- get() = _limit
-
- /**
- * The current state of the resource context.
- */
- private var _timestamp: Long = Long.MIN_VALUE
- private var _work: Double = 0.0
- private var _limit: Double = 0.0
- private var _deadline: Long = Long.MAX_VALUE
-
- /**
- * The update flag indicating why the update was triggered.
- */
- private var _flag: Int = 0
-
- /**
- * The current pending update.
- */
- private var _pendingUpdate: SimResourceInterpreterImpl.Update? = null
-
- override fun start() {
- check(_state == SimResourceState.Pending) { "Consumer is already started" }
- interpreter.batch {
- consumer.onEvent(this, SimResourceEvent.Start)
- _state = SimResourceState.Active
- interrupt()
- }
- }
-
- override fun close() {
- if (_state != SimResourceState.Stopped) {
- interpreter.batch {
- _state = SimResourceState.Stopped
- doStop()
- }
- }
- }
-
- override fun interrupt() {
- if (_state == SimResourceState.Stopped) {
- return
- }
-
- _flag = _flag or FLAG_INTERRUPT
- scheduleUpdate()
- }
-
- override fun invalidate() {
- if (_state == SimResourceState.Stopped) {
- return
- }
-
- _flag = _flag or FLAG_INVALIDATE
- scheduleUpdate()
- }
-
- override fun flush() {
- if (_state == SimResourceState.Stopped) {
- return
- }
-
- interpreter.scheduleSync(this)
- }
-
- /**
- * Determine whether the state of the resource context should be updated.
- */
- fun requiresUpdate(timestamp: Long): Boolean {
- // Either the resource context is flagged or there is a pending update at this timestamp
- return _flag != 0 || _pendingUpdate?.timestamp == timestamp
- }
-
- /**
- * Update the state of the resource context.
- */
- fun doUpdate(timestamp: Long) {
- try {
- val oldState = _state
- val newState = doUpdate(timestamp, oldState)
-
- _state = newState
- _flag = 0
-
- when (newState) {
- SimResourceState.Pending ->
- if (oldState != SimResourceState.Pending) {
- throw IllegalStateException("Illegal transition to pending state")
- }
- SimResourceState.Stopped ->
- if (oldState != SimResourceState.Stopped) {
- doStop()
- }
- else -> {}
- }
- } catch (cause: Throwable) {
- doFail(cause)
- } finally {
- _remainingWorkFlush = Long.MIN_VALUE
- _timestamp = timestamp
- }
- }
-
- override fun onConverge(timestamp: Long) {
- if (_state == SimResourceState.Active) {
- consumer.onEvent(this, SimResourceEvent.Run)
- }
- }
-
- override fun toString(): String = "SimResourceContextImpl[capacity=$capacity]"
-
- /**
- * Update the state of the resource context.
- */
- private fun doUpdate(timestamp: Long, state: SimResourceState): SimResourceState {
- return when (state) {
- // Resource context is not active, so its state will not update
- SimResourceState.Pending, SimResourceState.Stopped -> state
- SimResourceState.Active -> {
- val isInterrupted = _flag and FLAG_INTERRUPT != 0
- val remainingWork = getRemainingWork(timestamp)
- val isConsume = _limit > 0.0
- val reachedDeadline = _deadline <= timestamp
-
- // Update the resource counters only if there is some progress
- if (timestamp > _timestamp) {
- logic.onUpdate(this, _work, reachedDeadline)
- }
-
- // We should only continue processing the next command if:
- // 1. The resource consumption was finished.
- // 2. The resource capacity cannot satisfy the demand.
- // 3. The resource consumer should be interrupted (e.g., someone called .interrupt())
- if ((isConsume && remainingWork == 0.0) || reachedDeadline || isInterrupted) {
- when (val command = consumer.onNext(this)) {
- is SimResourceCommand.Idle -> interpretIdle(timestamp, command.deadline)
- is SimResourceCommand.Consume -> interpretConsume(timestamp, command.work, command.limit, command.deadline)
- is SimResourceCommand.Exit -> interpretExit()
- }
- } else if (isConsume) {
- interpretConsume(timestamp, remainingWork, _limit, _deadline)
- } else {
- interpretIdle(timestamp, _deadline)
- }
- }
- }
- }
-
- /**
- * Stop the resource context.
- */
- private fun doStop() {
- try {
- consumer.onEvent(this, SimResourceEvent.Exit)
- logic.onFinish(this)
- } catch (cause: Throwable) {
- doFail(cause)
- }
- }
-
- /**
- * Fail the resource consumer.
- */
- private fun doFail(cause: Throwable) {
- try {
- consumer.onFailure(this, cause)
- } catch (e: Throwable) {
- e.addSuppressed(cause)
- e.printStackTrace()
- }
-
- logic.onFinish(this)
- }
-
- /**
- * Interpret the [SimResourceCommand.Consume] command.
- */
- private fun interpretConsume(now: Long, work: Double, limit: Double, deadline: Long): SimResourceState {
- require(deadline >= now) { "Deadline already passed" }
-
- _speed = min(capacity, limit)
- _work = work
- _limit = limit
- _deadline = deadline
-
- val timestamp = logic.onConsume(this, work, limit, deadline)
- scheduleUpdate(timestamp)
-
- return SimResourceState.Active
- }
-
- /**
- * Interpret the [SimResourceCommand.Idle] command.
- */
- private fun interpretIdle(now: Long, deadline: Long): SimResourceState {
- require(deadline >= now) { "Deadline already passed" }
-
- _speed = 0.0
- _work = 0.0
- _limit = 0.0
- _deadline = deadline
-
- val timestamp = logic.onIdle(this, deadline)
- scheduleUpdate(timestamp)
-
- return SimResourceState.Active
- }
-
- /**
- * Interpret the [SimResourceCommand.Exit] command.
- */
- private fun interpretExit(): SimResourceState {
- _speed = 0.0
- _work = 0.0
- _limit = 0.0
- _deadline = Long.MAX_VALUE
-
- return SimResourceState.Stopped
- }
-
- private var _remainingWork: Double = 0.0
- private var _remainingWorkFlush: Long = Long.MIN_VALUE
-
- /**
- * Obtain the remaining work at the given timestamp.
- */
- private fun getRemainingWork(now: Long): Double {
- return if (_remainingWorkFlush < now) {
- _remainingWorkFlush = now
- computeRemainingWork(now).also { _remainingWork = it }
- } else {
- _remainingWork
- }
- }
-
- /**
- * Compute the remaining work based on the current state.
- */
- private fun computeRemainingWork(now: Long): Double {
- return if (_work > 0.0)
- max(0.0, _work - logic.getConsumedWork(this, _work, speed, now - _timestamp))
- else 0.0
- }
-
- /**
- * Indicate that the capacity of the resource has changed.
- */
- private fun onCapacityChange() {
- // Do not inform the consumer if it has not been started yet
- if (state != SimResourceState.Active) {
- return
- }
-
- val isThrottled = speed > capacity
-
- interpreter.batch {
- // Inform the consumer of the capacity change. This might already trigger an interrupt.
- consumer.onEvent(this, SimResourceEvent.Capacity)
-
- // Optimization: only invalidate context if the new capacity cannot satisfy the active resource command.
- if (isThrottled) {
- invalidate()
- }
- }
- }
-
- /**
- * Schedule an update for this resource context.
- */
- private fun scheduleUpdate() {
- // Cancel the pending update
- val pendingUpdate = _pendingUpdate
- if (pendingUpdate != null) {
- _pendingUpdate = null
- pendingUpdate.cancel()
- }
-
- interpreter.scheduleImmediate(this)
- }
-
- /**
- * Schedule a delayed update for this resource context.
- */
- private fun scheduleUpdate(timestamp: Long) {
- val pendingUpdate = _pendingUpdate
- if (pendingUpdate != null) {
- if (pendingUpdate.timestamp == timestamp) {
- // Fast-path: A pending update for the same timestamp already exists
- return
- } else {
- // Cancel the old pending update
- _pendingUpdate = null
- pendingUpdate.cancel()
- }
- }
-
- if (timestamp != Long.MAX_VALUE) {
- _pendingUpdate = interpreter.scheduleDelayed(this, timestamp)
- }
- }
-
- /**
- * A flag to indicate that the context should be invalidated.
- */
- private val FLAG_INVALIDATE = 0b01
-
- /**
- * A flag to indicate that the context should be interrupted.
- */
- private val FLAG_INTERRUPT = 0b10
-}
diff --git a/opendc-simulator/opendc-simulator-resources/src/main/kotlin/org/opendc/simulator/resources/impl/SimResourceInterpreterImpl.kt b/opendc-simulator/opendc-simulator-resources/src/main/kotlin/org/opendc/simulator/resources/impl/SimResourceInterpreterImpl.kt
deleted file mode 100644
index c3dcebd0..00000000
--- a/opendc-simulator/opendc-simulator-resources/src/main/kotlin/org/opendc/simulator/resources/impl/SimResourceInterpreterImpl.kt
+++ /dev/null
@@ -1,351 +0,0 @@
-/*
- * Copyright (c) 2021 AtLarge Research
- *
- * Permission is hereby granted, free of charge, to any person obtaining a copy
- * of this software and associated documentation files (the "Software"), to deal
- * in the Software without restriction, including without limitation the rights
- * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
- * copies of the Software, and to permit persons to whom the Software is
- * furnished to do so, subject to the following conditions:
- *
- * The above copyright notice and this permission notice shall be included in all
- * copies or substantial portions of the Software.
- *
- * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
- * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
- * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
- * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
- * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
- * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
- * SOFTWARE.
- */
-
-package org.opendc.simulator.resources.impl
-
-import kotlinx.coroutines.Delay
-import kotlinx.coroutines.DisposableHandle
-import kotlinx.coroutines.InternalCoroutinesApi
-import kotlinx.coroutines.Runnable
-import org.opendc.simulator.resources.*
-import java.time.Clock
-import java.util.*
-import kotlin.coroutines.ContinuationInterceptor
-import kotlin.coroutines.CoroutineContext
-
-/**
- * A [SimResourceInterpreter] queues all interrupts that occur during execution to be executed after.
- *
- * @param context The coroutine context to use.
- * @param clock The virtual simulation clock.
- */
-internal class SimResourceInterpreterImpl(private val context: CoroutineContext, override val clock: Clock) : SimResourceInterpreter {
- /**
- * 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 resource updates that are scheduled for immediate execution.
- */
- private val queue = ArrayDeque<SimResourceContextImpl>()
-
- /**
- * A priority queue containing the resource updates to be scheduled in the future.
- */
- private val futureQueue = PriorityQueue<Update>(compareBy { it.timestamp })
-
- /**
- * The stack of interpreter invocations to occur in the future.
- */
- private val futureInvocations = ArrayDeque<Invocation>()
-
- /**
- * The systems that have been visited during the interpreter cycle.
- */
- private val visited = linkedSetOf<SimResourceSystem>()
-
- /**
- * The index in the batch stack.
- */
- private var batchIndex = 0
-
- /**
- * A flag to indicate that the interpreter is currently active.
- */
- private val isRunning: Boolean
- get() = batchIndex > 0
-
- /**
- * Enqueue the specified [ctx] to be updated immediately during the active interpreter cycle.
- *
- * This method should be used when the state of a resource context is invalidated/interrupted and needs to be
- * re-computed. In case no interpreter is currently active, the interpreter will be started.
- */
- fun scheduleImmediate(ctx: SimResourceContextImpl) {
- queue.add(ctx)
-
- // In-case the interpreter is already running in the call-stack, return immediately. The changes will be picked
- // up by the active interpreter.
- if (isRunning) {
- return
- }
-
- try {
- batchIndex++
- runInterpreter()
- } finally {
- batchIndex--
- }
- }
-
- /**
- * Update the specified [ctx] synchronously.
- */
- fun scheduleSync(ctx: SimResourceContextImpl) {
- ctx.doUpdate(clock.millis())
-
- if (visited.add(ctx)) {
- collectAncestors(ctx, visited)
- }
-
- // In-case the interpreter is already running in the call-stack, return immediately. The changes will be picked
- // up by the active interpreter.
- if (isRunning) {
- return
- }
-
- try {
- batchIndex++
- runInterpreter()
- } finally {
- batchIndex--
- }
- }
-
- /**
- * Schedule the interpreter to run at [timestamp] to update the resource contexts.
- *
- * This method will override earlier calls to this method for the same [ctx].
- *
- * @param ctx The resource context to which the event applies.
- * @param timestamp The timestamp when the interrupt should happen.
- */
- fun scheduleDelayed(ctx: SimResourceContextImpl, timestamp: Long): Update {
- val now = clock.millis()
- val futureQueue = futureQueue
-
- require(timestamp >= now) { "Timestamp must be in the future" }
-
- val update = allocUpdate(ctx, timestamp)
- futureQueue.add(update)
-
- // Optimization: Check if we need to push the interruption forward. Note that we check by timer reference.
- if (futureQueue.peek() === update) {
- trySchedule(futureQueue, futureInvocations)
- }
-
- return update
- }
-
- override fun newContext(
- consumer: SimResourceConsumer,
- provider: SimResourceProviderLogic,
- parent: SimResourceSystem?
- ): SimResourceControllableContext = SimResourceContextImpl(parent, this, consumer, provider)
-
- override fun pushBatch() {
- batchIndex++
- }
-
- override fun popBatch() {
- try {
- // Flush the work if the platform is not already running
- if (batchIndex == 1 && queue.isNotEmpty()) {
- runInterpreter()
- }
- } finally {
- batchIndex--
- }
- }
-
- /**
- * Interpret all actions that are scheduled for the current timestamp.
- */
- private fun runInterpreter() {
- val now = clock.millis()
- val queue = queue
- val futureQueue = futureQueue
- val futureInvocations = futureInvocations
- val visited = visited
-
- // Execute all scheduled updates at current timestamp
- while (true) {
- val update = futureQueue.peek() ?: break
-
- assert(update.timestamp >= now) { "Internal inconsistency: found update of the past" }
-
- if (update.timestamp > now && !update.isCancelled) {
- // Schedule a task for the next event to occur.
- trySchedule(futureQueue, futureInvocations)
- break
- }
-
- futureQueue.poll()
-
- val shouldExecute = !update.isCancelled && update.ctx.requiresUpdate(now)
- if (shouldExecute) {
- update.ctx.doUpdate(now)
-
- if (visited.add(update.ctx)) {
- collectAncestors(update.ctx, visited)
- }
- }
-
- updatePool.add(update)
- }
-
- // 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
- val shouldExecute = ctx.requiresUpdate(now)
-
- if (shouldExecute) {
- ctx.doUpdate(now)
-
- if (visited.add(ctx)) {
- collectAncestors(ctx, visited)
- }
- }
- }
-
- for (system in visited) {
- system.onConverge(now)
- }
-
- visited.clear()
- } while (queue.isNotEmpty())
- }
-
- /**
- * Try to schedule the next interpreter event.
- */
- private fun trySchedule(queue: PriorityQueue<Update>, scheduled: ArrayDeque<Invocation>) {
- val nextTimer = queue.peek()
- val now = clock.millis()
-
- // Check whether we need to update our schedule:
- if (nextTimer == null) {
- // Case 1: all timers are cancelled
- for (invocation in scheduled) {
- invocation.cancel()
- }
- scheduled.clear()
- return
- }
-
- while (true) {
- val invocation = scheduled.peekFirst()
- if (invocation == null || invocation.timestamp > nextTimer.timestamp) {
- // Case 2: A new timer was registered ahead of the other timers.
- // Solution: Schedule a new scheduler invocation
- val nextTimestamp = nextTimer.timestamp
- @OptIn(InternalCoroutinesApi::class)
- val handle = delay.invokeOnTimeout(
- nextTimestamp - now,
- {
- try {
- batchIndex++
- runInterpreter()
- } finally {
- batchIndex--
- }
- },
- context
- )
- scheduled.addFirst(Invocation(nextTimestamp, handle))
- break
- } else if (invocation.timestamp < nextTimer.timestamp) {
- // Case 2: A timer was cancelled and the head of the timer queue is now later than excepted
- // Solution: Cancel the next scheduler invocation
- invocation.cancel()
- scheduled.pollFirst()
- } else {
- break
- }
- }
- }
-
- /**
- * Collect all the ancestors of the specified [system].
- */
- private tailrec fun collectAncestors(system: SimResourceSystem, systems: MutableSet<SimResourceSystem>) {
- val parent = system.parent
- if (parent != null) {
- systems.add(parent)
- collectAncestors(parent, systems)
- }
- }
-
- /**
- * The pool of existing updates.
- */
- private val updatePool = ArrayDeque<Update>()
-
- /**
- * Allocate an [Update] object.
- */
- private fun allocUpdate(ctx: SimResourceContextImpl, timestamp: Long): Update {
- val update = updatePool.poll()
- return if (update != null) {
- update.ctx = ctx
- update.timestamp = timestamp
- update.isCancelled = false
- update
- } else {
- Update(ctx, timestamp)
- }
- }
-
- /**
- * A future interpreter invocation.
- *
- * This class is used to keep track of the future scheduler invocations created using the [Delay] instance. In case
- * the invocation is not needed anymore, it can be cancelled via [cancel].
- */
- private data class Invocation(
- @JvmField val timestamp: Long,
- private val disposableHandle: DisposableHandle
- ) {
- /**
- * Cancel the interpreter invocation.
- */
- fun cancel() = disposableHandle.dispose()
- }
-
- /**
- * An update call for [ctx] that is scheduled for [timestamp].
- *
- * This class represents an update in the future at [timestamp] requested by [ctx]. A deferred update might be
- * cancelled if the resource context was invalidated in the meantime.
- */
- class Update(@JvmField var ctx: SimResourceContextImpl, @JvmField var timestamp: Long) {
- /**
- * A flag to indicate that the task has been cancelled.
- */
- @JvmField
- var isCancelled: Boolean = false
-
- /**
- * Cancel the update.
- */
- fun cancel() {
- isCancelled = true
- }
-
- override fun toString(): String = "Update[ctx=$ctx,timestamp=$timestamp]"
- }
-}
diff --git a/opendc-simulator/opendc-simulator-resources/src/test/kotlin/org/opendc/simulator/resources/SimResourceAggregatorMaxMinTest.kt b/opendc-simulator/opendc-simulator-resources/src/test/kotlin/org/opendc/simulator/resources/SimResourceAggregatorMaxMinTest.kt
deleted file mode 100644
index 2f01a8c4..00000000
--- a/opendc-simulator/opendc-simulator-resources/src/test/kotlin/org/opendc/simulator/resources/SimResourceAggregatorMaxMinTest.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.resources
-
-import io.mockk.every
-import io.mockk.mockk
-import io.mockk.verify
-import kotlinx.coroutines.*
-import org.junit.jupiter.api.Assertions.*
-import org.junit.jupiter.api.Test
-import org.junit.jupiter.api.assertAll
-import org.junit.jupiter.api.assertThrows
-import org.opendc.simulator.core.runBlockingSimulation
-import org.opendc.simulator.resources.consumer.SimSpeedConsumerAdapter
-import org.opendc.simulator.resources.consumer.SimWorkConsumer
-import org.opendc.simulator.resources.impl.SimResourceInterpreterImpl
-
-/**
- * Test suite for the [SimResourceAggregatorMaxMin] class.
- */
-@OptIn(ExperimentalCoroutinesApi::class)
-internal class SimResourceAggregatorMaxMinTest {
- @Test
- fun testSingleCapacity() = runBlockingSimulation {
- val scheduler = SimResourceInterpreterImpl(coroutineContext, clock)
-
- val aggregator = SimResourceAggregatorMaxMin(scheduler)
- val forwarder = SimResourceForwarder()
- val sources = listOf(
- forwarder,
- SimResourceSource(1.0, scheduler)
- )
- sources.forEach(aggregator::addInput)
-
- val consumer = SimWorkConsumer(1.0, 0.5)
- val usage = mutableListOf<Double>()
- val source = SimResourceSource(1.0, scheduler)
- val adapter = SimSpeedConsumerAdapter(forwarder, usage::add)
- source.startConsumer(adapter)
-
- aggregator.consume(consumer)
- yield()
-
- assertAll(
- { assertEquals(1000, clock.millis()) },
- { assertEquals(listOf(0.0, 0.5, 0.0), usage) }
- )
- }
-
- @Test
- fun testDoubleCapacity() = runBlockingSimulation {
- val scheduler = SimResourceInterpreterImpl(coroutineContext, clock)
-
- val aggregator = SimResourceAggregatorMaxMin(scheduler)
- val sources = listOf(
- SimResourceSource(1.0, scheduler),
- SimResourceSource(1.0, scheduler)
- )
- sources.forEach(aggregator::addInput)
-
- val consumer = SimWorkConsumer(2.0, 1.0)
- val usage = mutableListOf<Double>()
- val adapter = SimSpeedConsumerAdapter(consumer, usage::add)
-
- aggregator.consume(adapter)
- yield()
- assertAll(
- { assertEquals(1000, clock.millis()) },
- { assertEquals(listOf(0.0, 2.0, 0.0), usage) }
- )
- }
-
- @Test
- fun testOvercommit() = runBlockingSimulation {
- val scheduler = SimResourceInterpreterImpl(coroutineContext, clock)
-
- val aggregator = SimResourceAggregatorMaxMin(scheduler)
- val sources = listOf(
- SimResourceSource(1.0, scheduler),
- SimResourceSource(1.0, scheduler)
- )
- sources.forEach(aggregator::addInput)
-
- val consumer = mockk<SimResourceConsumer>(relaxUnitFun = true)
- every { consumer.onNext(any()) }
- .returns(SimResourceCommand.Consume(4.0, 4.0, 1000))
- .andThen(SimResourceCommand.Exit)
-
- aggregator.consume(consumer)
- yield()
- assertEquals(1000, clock.millis())
-
- verify(exactly = 2) { consumer.onNext(any()) }
- }
-
- @Test
- fun testException() = runBlockingSimulation {
- val scheduler = SimResourceInterpreterImpl(coroutineContext, clock)
-
- val aggregator = SimResourceAggregatorMaxMin(scheduler)
- val sources = listOf(
- SimResourceSource(1.0, scheduler),
- SimResourceSource(1.0, scheduler)
- )
- sources.forEach(aggregator::addInput)
-
- val consumer = mockk<SimResourceConsumer>(relaxUnitFun = true)
- every { consumer.onNext(any()) }
- .returns(SimResourceCommand.Consume(1.0, 1.0))
- .andThenThrows(IllegalStateException("Test Exception"))
-
- assertThrows<IllegalStateException> { aggregator.consume(consumer) }
- yield()
- assertFalse(sources[0].isActive)
- }
-
- @Test
- fun testAdjustCapacity() = runBlockingSimulation {
- val scheduler = SimResourceInterpreterImpl(coroutineContext, clock)
-
- val aggregator = SimResourceAggregatorMaxMin(scheduler)
- val sources = listOf(
- SimResourceSource(1.0, scheduler),
- SimResourceSource(1.0, scheduler)
- )
- sources.forEach(aggregator::addInput)
-
- val consumer = SimWorkConsumer(4.0, 1.0)
- coroutineScope {
- launch { aggregator.consume(consumer) }
- delay(1000)
- sources[0].capacity = 0.5
- }
- yield()
- assertEquals(2334, clock.millis())
- }
-
- @Test
- fun testFailOverCapacity() = runBlockingSimulation {
- val scheduler = SimResourceInterpreterImpl(coroutineContext, clock)
-
- val aggregator = SimResourceAggregatorMaxMin(scheduler)
- val sources = listOf(
- SimResourceSource(1.0, scheduler),
- SimResourceSource(1.0, scheduler)
- )
- sources.forEach(aggregator::addInput)
-
- val consumer = SimWorkConsumer(1.0, 0.5)
- coroutineScope {
- launch { aggregator.consume(consumer) }
- delay(500)
- sources[0].capacity = 0.5
- }
- yield()
- assertEquals(1000, clock.millis())
- }
-
- @Test
- fun testCounters() = runBlockingSimulation {
- val scheduler = SimResourceInterpreterImpl(coroutineContext, clock)
-
- val aggregator = SimResourceAggregatorMaxMin(scheduler)
- val sources = listOf(
- SimResourceSource(1.0, scheduler),
- SimResourceSource(1.0, scheduler)
- )
- sources.forEach(aggregator::addInput)
-
- val consumer = mockk<SimResourceConsumer>(relaxUnitFun = true)
- every { consumer.onNext(any()) }
- .returns(SimResourceCommand.Consume(4.0, 4.0, 1000))
- .andThen(SimResourceCommand.Exit)
-
- aggregator.consume(consumer)
- yield()
- assertEquals(1000, clock.millis())
- assertEquals(2.0, aggregator.counters.actual) { "Actual work mismatch" }
- }
-}
diff --git a/opendc-simulator/opendc-simulator-resources/src/test/kotlin/org/opendc/simulator/resources/SimResourceCommandTest.kt b/opendc-simulator/opendc-simulator-resources/src/test/kotlin/org/opendc/simulator/resources/SimResourceCommandTest.kt
deleted file mode 100644
index 02d456ff..00000000
--- a/opendc-simulator/opendc-simulator-resources/src/test/kotlin/org/opendc/simulator/resources/SimResourceCommandTest.kt
+++ /dev/null
@@ -1,74 +0,0 @@
-/*
- * Copyright (c) 2021 AtLarge Research
- *
- * Permission is hereby granted, free of charge, to any person obtaining a copy
- * of this software and associated documentation files (the "Software"), to deal
- * in the Software without restriction, including without limitation the rights
- * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
- * copies of the Software, and to permit persons to whom the Software is
- * furnished to do so, subject to the following conditions:
- *
- * The above copyright notice and this permission notice shall be included in all
- * copies or substantial portions of the Software.
- *
- * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
- * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
- * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
- * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
- * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
- * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
- * SOFTWARE.
- */
-
-package org.opendc.simulator.resources
-
-import org.junit.jupiter.api.Test
-import org.junit.jupiter.api.assertDoesNotThrow
-import org.junit.jupiter.api.assertThrows
-
-/**
- * Test suite for [SimResourceCommand].
- */
-class SimResourceCommandTest {
- @Test
- fun testZeroWork() {
- assertThrows<IllegalArgumentException> {
- SimResourceCommand.Consume(0.0, 1.0)
- }
- }
-
- @Test
- fun testNegativeWork() {
- assertThrows<IllegalArgumentException> {
- SimResourceCommand.Consume(-1.0, 1.0)
- }
- }
-
- @Test
- fun testZeroLimit() {
- assertThrows<IllegalArgumentException> {
- SimResourceCommand.Consume(1.0, 0.0)
- }
- }
-
- @Test
- fun testNegativeLimit() {
- assertThrows<IllegalArgumentException> {
- SimResourceCommand.Consume(1.0, -1.0, 1)
- }
- }
-
- @Test
- fun testConsumeCorrect() {
- assertDoesNotThrow {
- SimResourceCommand.Consume(1.0, 1.0)
- }
- }
-
- @Test
- fun testIdleCorrect() {
- assertDoesNotThrow {
- SimResourceCommand.Idle(1)
- }
- }
-}
diff --git a/opendc-simulator/opendc-simulator-resources/src/test/kotlin/org/opendc/simulator/resources/SimResourceContextTest.kt b/opendc-simulator/opendc-simulator-resources/src/test/kotlin/org/opendc/simulator/resources/SimResourceContextTest.kt
deleted file mode 100644
index 6cb507ce..00000000
--- a/opendc-simulator/opendc-simulator-resources/src/test/kotlin/org/opendc/simulator/resources/SimResourceContextTest.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.resources
-
-import io.mockk.*
-import kotlinx.coroutines.*
-import org.junit.jupiter.api.*
-import org.opendc.simulator.core.runBlockingSimulation
-import org.opendc.simulator.resources.impl.SimResourceContextImpl
-import org.opendc.simulator.resources.impl.SimResourceInterpreterImpl
-
-/**
- * A test suite for the [SimResourceContextImpl] class.
- */
-@OptIn(ExperimentalCoroutinesApi::class)
-class SimResourceContextTest {
- @Test
- fun testFlushWithoutCommand() = runBlockingSimulation {
- val interpreter = SimResourceInterpreterImpl(coroutineContext, clock)
- val consumer = mockk<SimResourceConsumer>(relaxUnitFun = true)
- every { consumer.onNext(any()) } returns SimResourceCommand.Consume(10.0, 1.0) andThen SimResourceCommand.Exit
-
- val logic = object : SimResourceProviderLogic {
- override fun onIdle(ctx: SimResourceControllableContext, deadline: Long): Long = deadline
- override fun onConsume(ctx: SimResourceControllableContext, work: Double, limit: Double, deadline: Long): Long = deadline
- override fun onFinish(ctx: SimResourceControllableContext) {}
- }
- val context = SimResourceContextImpl(null, interpreter, consumer, logic)
-
- context.doUpdate(interpreter.clock.millis())
- }
-
- @Test
- fun testIntermediateFlush() = runBlockingSimulation {
- val interpreter = SimResourceInterpreterImpl(coroutineContext, clock)
- val consumer = mockk<SimResourceConsumer>(relaxUnitFun = true)
- every { consumer.onNext(any()) } returns SimResourceCommand.Consume(10.0, 1.0) andThen SimResourceCommand.Exit
-
- val logic = spyk(object : SimResourceProviderLogic {
- override fun onIdle(ctx: SimResourceControllableContext, deadline: Long): Long = deadline
- override fun onFinish(ctx: SimResourceControllableContext) {}
- override fun onConsume(ctx: SimResourceControllableContext, work: Double, limit: Double, deadline: Long): Long = deadline
- })
- val context = spyk(SimResourceContextImpl(null, interpreter, consumer, logic))
-
- context.start()
- delay(1) // Delay 1 ms to prevent hitting the fast path
- context.doUpdate(interpreter.clock.millis())
-
- verify(exactly = 2) { logic.onConsume(any(), any(), any(), any()) }
- }
-
- @Test
- fun testIntermediateFlushIdle() = runBlockingSimulation {
- val interpreter = SimResourceInterpreterImpl(coroutineContext, clock)
- val consumer = mockk<SimResourceConsumer>(relaxUnitFun = true)
- every { consumer.onNext(any()) } returns SimResourceCommand.Idle(10) andThen SimResourceCommand.Exit
-
- val logic = spyk(object : SimResourceProviderLogic {
- override fun onIdle(ctx: SimResourceControllableContext, deadline: Long): Long = deadline
- override fun onFinish(ctx: SimResourceControllableContext) {}
- override fun onConsume(ctx: SimResourceControllableContext, work: Double, limit: Double, deadline: Long): Long = deadline
- })
- val context = spyk(SimResourceContextImpl(null, interpreter, consumer, logic))
-
- context.start()
- delay(5)
- context.invalidate()
- delay(5)
- context.invalidate()
-
- assertAll(
- { verify(exactly = 2) { logic.onIdle(any(), any()) } },
- { verify(exactly = 1) { logic.onFinish(any()) } }
- )
- }
-
- @Test
- fun testDoubleStart() = runBlockingSimulation {
- val interpreter = SimResourceInterpreterImpl(coroutineContext, clock)
- val consumer = mockk<SimResourceConsumer>(relaxUnitFun = true)
- every { consumer.onNext(any()) } returns SimResourceCommand.Idle(10) andThen SimResourceCommand.Exit
-
- val logic = object : SimResourceProviderLogic {
- override fun onIdle(ctx: SimResourceControllableContext, deadline: Long): Long = deadline
- override fun onFinish(ctx: SimResourceControllableContext) {}
- override fun onConsume(ctx: SimResourceControllableContext, work: Double, limit: Double, deadline: Long): Long = deadline
- }
- val context = SimResourceContextImpl(null, interpreter, consumer, logic)
-
- context.start()
-
- assertThrows<IllegalStateException> {
- context.start()
- }
- }
-
- @Test
- fun testIdempodentCapacityChange() = runBlockingSimulation {
- val interpreter = SimResourceInterpreterImpl(coroutineContext, clock)
- val consumer = mockk<SimResourceConsumer>(relaxUnitFun = true)
- every { consumer.onNext(any()) } returns SimResourceCommand.Consume(10.0, 1.0) andThen SimResourceCommand.Exit
-
- val logic = object : SimResourceProviderLogic {
- override fun onIdle(ctx: SimResourceControllableContext, deadline: Long): Long = deadline
- override fun onFinish(ctx: SimResourceControllableContext) {}
- override fun onConsume(ctx: SimResourceControllableContext, work: Double, limit: Double, deadline: Long): Long = deadline
- }
-
- val context = SimResourceContextImpl(null, interpreter, consumer, logic)
- context.capacity = 4200.0
- context.start()
- context.capacity = 4200.0
-
- verify(exactly = 0) { consumer.onEvent(any(), SimResourceEvent.Capacity) }
- }
-
- @Test
- fun testFailureNoInfiniteLoop() = runBlockingSimulation {
- val interpreter = SimResourceInterpreterImpl(coroutineContext, clock)
- val consumer = mockk<SimResourceConsumer>(relaxUnitFun = true)
- every { consumer.onNext(any()) } returns SimResourceCommand.Exit
- every { consumer.onEvent(any(), SimResourceEvent.Exit) } throws IllegalStateException("onEvent")
- every { consumer.onFailure(any(), any()) } throws IllegalStateException("onFailure")
-
- val logic = spyk(object : SimResourceProviderLogic {
- override fun onIdle(ctx: SimResourceControllableContext, deadline: Long): Long = deadline
- override fun onFinish(ctx: SimResourceControllableContext) {}
- override fun onConsume(ctx: SimResourceControllableContext, work: Double, limit: Double, deadline: Long): Long = deadline
- })
-
- val context = SimResourceContextImpl(null, interpreter, consumer, logic)
-
- context.start()
-
- delay(1)
-
- verify(exactly = 1) { consumer.onFailure(any(), any()) }
- }
-}
diff --git a/opendc-simulator/opendc-simulator-resources/src/test/kotlin/org/opendc/simulator/resources/SimResourceSourceTest.kt b/opendc-simulator/opendc-simulator-resources/src/test/kotlin/org/opendc/simulator/resources/SimResourceSourceTest.kt
deleted file mode 100644
index 4895544d..00000000
--- a/opendc-simulator/opendc-simulator-resources/src/test/kotlin/org/opendc/simulator/resources/SimResourceSourceTest.kt
+++ /dev/null
@@ -1,273 +0,0 @@
-/*
- * Copyright (c) 2021 AtLarge Research
- *
- * Permission is hereby granted, free of charge, to any person obtaining a copy
- * of this software and associated documentation files (the "Software"), to deal
- * in the Software without restriction, including without limitation the rights
- * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
- * copies of the Software, and to permit persons to whom the Software is
- * furnished to do so, subject to the following conditions:
- *
- * The above copyright notice and this permission notice shall be included in all
- * copies or substantial portions of the Software.
- *
- * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
- * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
- * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
- * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
- * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
- * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
- * SOFTWARE.
- */
-
-package org.opendc.simulator.resources
-
-import io.mockk.every
-import io.mockk.mockk
-import io.mockk.spyk
-import io.mockk.verify
-import kotlinx.coroutines.*
-import org.junit.jupiter.api.*
-import org.junit.jupiter.api.Assertions.assertEquals
-import org.opendc.simulator.core.runBlockingSimulation
-import org.opendc.simulator.resources.consumer.SimSpeedConsumerAdapter
-import org.opendc.simulator.resources.consumer.SimWorkConsumer
-import org.opendc.simulator.resources.impl.SimResourceInterpreterImpl
-
-/**
- * A test suite for the [SimResourceSource] class.
- */
-@OptIn(ExperimentalCoroutinesApi::class)
-class SimResourceSourceTest {
- @Test
- fun testSpeed() = runBlockingSimulation {
- val scheduler = SimResourceInterpreterImpl(coroutineContext, clock)
- val capacity = 4200.0
- val provider = SimResourceSource(capacity, scheduler)
-
- val consumer = mockk<SimResourceConsumer>(relaxUnitFun = true)
- every { consumer.onNext(any()) }
- .returns(SimResourceCommand.Consume(1000 * capacity, capacity))
- .andThen(SimResourceCommand.Exit)
-
- val res = mutableListOf<Double>()
- val adapter = SimSpeedConsumerAdapter(consumer, res::add)
-
- provider.consume(adapter)
-
- assertEquals(listOf(0.0, capacity, 0.0), res) { "Speed is reported correctly" }
- }
-
- @Test
- fun testAdjustCapacity() = runBlockingSimulation {
- val scheduler = SimResourceInterpreterImpl(coroutineContext, clock)
- val provider = SimResourceSource(1.0, scheduler)
-
- val consumer = spyk(SimWorkConsumer(2.0, 1.0))
-
- coroutineScope {
- launch { provider.consume(consumer) }
- delay(1000)
- provider.capacity = 0.5
- }
- assertEquals(3000, clock.millis())
- verify(exactly = 1) { consumer.onEvent(any(), SimResourceEvent.Capacity) }
- }
-
- @Test
- fun testSpeedLimit() = runBlockingSimulation {
- val scheduler = SimResourceInterpreterImpl(coroutineContext, clock)
- val capacity = 4200.0
- val provider = SimResourceSource(capacity, scheduler)
-
- val consumer = mockk<SimResourceConsumer>(relaxUnitFun = true)
- every { consumer.onNext(any()) }
- .returns(SimResourceCommand.Consume(1000 * capacity, 2 * capacity))
- .andThen(SimResourceCommand.Exit)
-
- val res = mutableListOf<Double>()
- val adapter = SimSpeedConsumerAdapter(consumer, res::add)
-
- provider.consume(adapter)
-
- assertEquals(listOf(0.0, capacity, 0.0), res) { "Speed is reported correctly" }
- }
-
- /**
- * Test to see whether no infinite recursion occurs when interrupting during [SimResourceConsumer.onStart] or
- * [SimResourceConsumer.onNext].
- */
- @Test
- fun testIntermediateInterrupt() = runBlockingSimulation {
- val scheduler = SimResourceInterpreterImpl(coroutineContext, clock)
- val capacity = 4200.0
- val provider = SimResourceSource(capacity, scheduler)
-
- val consumer = object : SimResourceConsumer {
- override fun onNext(ctx: SimResourceContext): SimResourceCommand {
- return SimResourceCommand.Exit
- }
-
- override fun onEvent(ctx: SimResourceContext, event: SimResourceEvent) {
- ctx.interrupt()
- }
- }
-
- provider.consume(consumer)
- }
-
- @Test
- fun testInterrupt() = runBlockingSimulation {
- val scheduler = SimResourceInterpreterImpl(coroutineContext, clock)
- val capacity = 4200.0
- val provider = SimResourceSource(capacity, scheduler)
- lateinit var resCtx: SimResourceContext
-
- val consumer = object : SimResourceConsumer {
- var isFirst = true
-
- override fun onEvent(ctx: SimResourceContext, event: SimResourceEvent) {
- when (event) {
- SimResourceEvent.Start -> resCtx = ctx
- else -> {}
- }
- }
-
- override fun onNext(ctx: SimResourceContext): SimResourceCommand {
- assertEquals(0.0, ctx.remainingWork)
- return if (isFirst) {
- isFirst = false
- SimResourceCommand.Consume(4.0, 1.0)
- } else {
- SimResourceCommand.Exit
- }
- }
- }
-
- launch {
- yield()
- resCtx.interrupt()
- }
- provider.consume(consumer)
-
- assertEquals(0, clock.millis())
- }
-
- @Test
- fun testFailure() = runBlockingSimulation {
- val scheduler = SimResourceInterpreterImpl(coroutineContext, clock)
- val capacity = 4200.0
- val provider = SimResourceSource(capacity, scheduler)
-
- val consumer = mockk<SimResourceConsumer>(relaxUnitFun = true)
- every { consumer.onEvent(any(), eq(SimResourceEvent.Start)) }
- .throws(IllegalStateException())
-
- assertThrows<IllegalStateException> {
- provider.consume(consumer)
- }
- }
-
- @Test
- fun testExceptionPropagationOnNext() = runBlockingSimulation {
- val scheduler = SimResourceInterpreterImpl(coroutineContext, clock)
- val capacity = 4200.0
- val provider = SimResourceSource(capacity, scheduler)
-
- val consumer = mockk<SimResourceConsumer>(relaxUnitFun = true)
- every { consumer.onNext(any()) }
- .returns(SimResourceCommand.Consume(1.0, 1.0))
- .andThenThrows(IllegalStateException())
-
- assertThrows<IllegalStateException> {
- provider.consume(consumer)
- }
- }
-
- @Test
- fun testConcurrentConsumption() = runBlockingSimulation {
- val scheduler = SimResourceInterpreterImpl(coroutineContext, clock)
- val capacity = 4200.0
- val provider = SimResourceSource(capacity, scheduler)
-
- val consumer = mockk<SimResourceConsumer>(relaxUnitFun = true)
- every { consumer.onNext(any()) }
- .returns(SimResourceCommand.Consume(1.0, 1.0))
- .andThenThrows(IllegalStateException())
-
- assertThrows<IllegalStateException> {
- coroutineScope {
- launch { provider.consume(consumer) }
- provider.consume(consumer)
- }
- }
- }
-
- @Test
- fun testCancelDuringConsumption() = runBlockingSimulation {
- val scheduler = SimResourceInterpreterImpl(coroutineContext, clock)
- val capacity = 4200.0
- val provider = SimResourceSource(capacity, scheduler)
-
- val consumer = mockk<SimResourceConsumer>(relaxUnitFun = true)
- every { consumer.onNext(any()) }
- .returns(SimResourceCommand.Consume(1.0, 1.0))
- .andThenThrows(IllegalStateException())
-
- launch { provider.consume(consumer) }
- delay(500)
- provider.cancel()
-
- assertEquals(500, clock.millis())
- }
-
- @Test
- fun testIdle() = runBlockingSimulation {
- val scheduler = SimResourceInterpreterImpl(coroutineContext, clock)
- val capacity = 4200.0
- val provider = SimResourceSource(capacity, scheduler)
-
- val consumer = mockk<SimResourceConsumer>(relaxUnitFun = true)
- every { consumer.onNext(any()) }
- .returns(SimResourceCommand.Idle(clock.millis() + 500))
- .andThen(SimResourceCommand.Exit)
-
- provider.consume(consumer)
-
- assertEquals(500, clock.millis())
- }
-
- @Test
- fun testInfiniteSleep() {
- assertThrows<IllegalStateException> {
- runBlockingSimulation {
- val scheduler = SimResourceInterpreterImpl(coroutineContext, clock)
- val capacity = 4200.0
- val provider = SimResourceSource(capacity, scheduler)
-
- val consumer = mockk<SimResourceConsumer>(relaxUnitFun = true)
- every { consumer.onNext(any()) }
- .returns(SimResourceCommand.Idle())
- .andThenThrows(IllegalStateException())
-
- provider.consume(consumer)
- }
- }
- }
-
- @Test
- fun testIncorrectDeadline() = runBlockingSimulation {
- val scheduler = SimResourceInterpreterImpl(coroutineContext, clock)
- val capacity = 4200.0
- val provider = SimResourceSource(capacity, scheduler)
-
- val consumer = mockk<SimResourceConsumer>(relaxUnitFun = true)
- every { consumer.onNext(any()) }
- .returns(SimResourceCommand.Idle(2))
- .andThen(SimResourceCommand.Exit)
-
- delay(10)
-
- assertThrows<IllegalArgumentException> { provider.consume(consumer) }
- }
-}
diff --git a/opendc-simulator/opendc-simulator-resources/src/test/kotlin/org/opendc/simulator/resources/SimResourceSwitchExclusiveTest.kt b/opendc-simulator/opendc-simulator-resources/src/test/kotlin/org/opendc/simulator/resources/SimResourceSwitchExclusiveTest.kt
deleted file mode 100644
index ad8d82e3..00000000
--- a/opendc-simulator/opendc-simulator-resources/src/test/kotlin/org/opendc/simulator/resources/SimResourceSwitchExclusiveTest.kt
+++ /dev/null
@@ -1,176 +0,0 @@
-/*
- * Copyright (c) 2021 AtLarge Research
- *
- * Permission is hereby granted, free of charge, to any person obtaining a copy
- * of this software and associated documentation files (the "Software"), to deal
- * in the Software without restriction, including without limitation the rights
- * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
- * copies of the Software, and to permit persons to whom the Software is
- * furnished to do so, subject to the following conditions:
- *
- * The above copyright notice and this permission notice shall be included in all
- * copies or substantial portions of the Software.
- *
- * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
- * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
- * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
- * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
- * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
- * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
- * SOFTWARE.
- */
-
-package org.opendc.simulator.resources
-
-import io.mockk.every
-import io.mockk.mockk
-import kotlinx.coroutines.ExperimentalCoroutinesApi
-import kotlinx.coroutines.yield
-import org.junit.jupiter.api.Assertions.assertEquals
-import org.junit.jupiter.api.Test
-import org.junit.jupiter.api.assertAll
-import org.junit.jupiter.api.assertThrows
-import org.opendc.simulator.core.runBlockingSimulation
-import org.opendc.simulator.resources.consumer.SimSpeedConsumerAdapter
-import org.opendc.simulator.resources.consumer.SimTraceConsumer
-import org.opendc.simulator.resources.impl.SimResourceInterpreterImpl
-
-/**
- * Test suite for the [SimResourceSwitchExclusive] class.
- */
-@OptIn(ExperimentalCoroutinesApi::class)
-internal class SimResourceSwitchExclusiveTest {
- /**
- * Test a trace workload.
- */
- @Test
- fun testTrace() = runBlockingSimulation {
- val scheduler = SimResourceInterpreterImpl(coroutineContext, clock)
-
- val speed = mutableListOf<Double>()
-
- val duration = 5 * 60L
- val workload =
- SimTraceConsumer(
- sequenceOf(
- SimTraceConsumer.Fragment(duration * 1000, 28.0),
- SimTraceConsumer.Fragment(duration * 1000, 3500.0),
- SimTraceConsumer.Fragment(duration * 1000, 0.0),
- SimTraceConsumer.Fragment(duration * 1000, 183.0)
- ),
- )
-
- val switch = SimResourceSwitchExclusive()
- val source = SimResourceSource(3200.0, scheduler)
- val forwarder = SimResourceForwarder()
- val adapter = SimSpeedConsumerAdapter(forwarder, speed::add)
- source.startConsumer(adapter)
- switch.addInput(forwarder)
-
- val provider = switch.newOutput()
-
- try {
- provider.consume(workload)
- yield()
- } finally {
- provider.close()
- }
-
- assertAll(
- { assertEquals(listOf(0.0, 28.0, 3200.0, 0.0, 183.0, 0.0), speed) { "Correct speed" } },
- { assertEquals(5 * 60L * 4000, clock.millis()) { "Took enough time" } }
- )
- }
-
- /**
- * Test runtime workload on hypervisor.
- */
- @Test
- fun testRuntimeWorkload() = runBlockingSimulation {
- val scheduler = SimResourceInterpreterImpl(coroutineContext, clock)
-
- val duration = 5 * 60L * 1000
- val workload = mockk<SimResourceConsumer>(relaxUnitFun = true)
- every { workload.onNext(any()) } returns SimResourceCommand.Consume(duration / 1000.0, 1.0) andThen SimResourceCommand.Exit
-
- val switch = SimResourceSwitchExclusive()
- val source = SimResourceSource(3200.0, scheduler)
-
- switch.addInput(source)
-
- val provider = switch.newOutput()
-
- try {
- provider.consume(workload)
- yield()
- } finally {
- provider.close()
- }
- assertEquals(duration, clock.millis()) { "Took enough time" }
- }
-
- /**
- * Test two workloads running sequentially.
- */
- @Test
- fun testTwoWorkloads() = runBlockingSimulation {
- val scheduler = SimResourceInterpreterImpl(coroutineContext, clock)
-
- val duration = 5 * 60L * 1000
- val workload = object : SimResourceConsumer {
- var isFirst = true
-
- override fun onEvent(ctx: SimResourceContext, event: SimResourceEvent) {
- when (event) {
- SimResourceEvent.Start -> isFirst = true
- else -> {}
- }
- }
-
- override fun onNext(ctx: SimResourceContext): SimResourceCommand {
- return if (isFirst) {
- isFirst = false
- SimResourceCommand.Consume(duration / 1000.0, 1.0)
- } else {
- SimResourceCommand.Exit
- }
- }
- }
-
- val switch = SimResourceSwitchExclusive()
- val source = SimResourceSource(3200.0, scheduler)
-
- switch.addInput(source)
-
- val provider = switch.newOutput()
-
- try {
- provider.consume(workload)
- yield()
- provider.consume(workload)
- } finally {
- provider.close()
- }
- assertEquals(duration * 2, clock.millis()) { "Took enough time" }
- }
-
- /**
- * Test concurrent workloads on the machine.
- */
- @Test
- fun testConcurrentWorkloadFails() = runBlockingSimulation {
- val scheduler = SimResourceInterpreterImpl(coroutineContext, clock)
-
- val duration = 5 * 60L * 1000
- val workload = mockk<SimResourceConsumer>(relaxUnitFun = true)
- every { workload.onNext(any()) } returns SimResourceCommand.Consume(duration / 1000.0, 1.0) andThen SimResourceCommand.Exit
-
- val switch = SimResourceSwitchExclusive()
- val source = SimResourceSource(3200.0, scheduler)
-
- switch.addInput(source)
-
- switch.newOutput()
- assertThrows<IllegalStateException> { switch.newOutput() }
- }
-}
diff --git a/opendc-simulator/opendc-simulator-resources/src/test/kotlin/org/opendc/simulator/resources/SimResourceTransformerTest.kt b/opendc-simulator/opendc-simulator-resources/src/test/kotlin/org/opendc/simulator/resources/SimResourceTransformerTest.kt
deleted file mode 100644
index cf69b7b5..00000000
--- a/opendc-simulator/opendc-simulator-resources/src/test/kotlin/org/opendc/simulator/resources/SimResourceTransformerTest.kt
+++ /dev/null
@@ -1,222 +0,0 @@
-/*
- * Copyright (c) 2021 AtLarge Research
- *
- * Permission is hereby granted, free of charge, to any person obtaining a copy
- * of this software and associated documentation files (the "Software"), to deal
- * in the Software without restriction, including without limitation the rights
- * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
- * copies of the Software, and to permit persons to whom the Software is
- * furnished to do so, subject to the following conditions:
- *
- * The above copyright notice and this permission notice shall be included in all
- * copies or substantial portions of the Software.
- *
- * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
- * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
- * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
- * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
- * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
- * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
- * SOFTWARE.
- */
-
-package org.opendc.simulator.resources
-
-import io.mockk.every
-import io.mockk.mockk
-import io.mockk.spyk
-import io.mockk.verify
-import kotlinx.coroutines.*
-import org.junit.jupiter.api.Assertions.*
-import org.junit.jupiter.api.Test
-import org.junit.jupiter.api.assertThrows
-import org.opendc.simulator.core.runBlockingSimulation
-import org.opendc.simulator.resources.consumer.SimWorkConsumer
-import org.opendc.simulator.resources.impl.SimResourceInterpreterImpl
-
-/**
- * A test suite for the [SimResourceTransformer] class.
- */
-@OptIn(ExperimentalCoroutinesApi::class)
-internal class SimResourceTransformerTest {
- @Test
- fun testCancelImmediately() = runBlockingSimulation {
- val forwarder = SimResourceForwarder()
- val scheduler = SimResourceInterpreterImpl(coroutineContext, clock)
- val source = SimResourceSource(2000.0, scheduler)
-
- launch { source.consume(forwarder) }
-
- forwarder.consume(object : SimResourceConsumer {
- override fun onNext(ctx: SimResourceContext): SimResourceCommand {
- return SimResourceCommand.Exit
- }
- })
-
- forwarder.close()
- source.cancel()
- }
-
- @Test
- fun testCancel() = runBlockingSimulation {
- val forwarder = SimResourceForwarder()
- val scheduler = SimResourceInterpreterImpl(coroutineContext, clock)
- val source = SimResourceSource(2000.0, scheduler)
-
- launch { source.consume(forwarder) }
-
- forwarder.consume(object : SimResourceConsumer {
- var isFirst = true
-
- override fun onNext(ctx: SimResourceContext): SimResourceCommand {
- return if (isFirst) {
- isFirst = false
- SimResourceCommand.Consume(10.0, 1.0)
- } else {
- SimResourceCommand.Exit
- }
- }
- })
-
- forwarder.close()
- source.cancel()
- }
-
- @Test
- fun testState() = runBlockingSimulation {
- val forwarder = SimResourceForwarder()
- val consumer = object : SimResourceConsumer {
- override fun onNext(ctx: SimResourceContext): SimResourceCommand = SimResourceCommand.Exit
- }
-
- assertFalse(forwarder.isActive)
-
- forwarder.startConsumer(consumer)
- assertTrue(forwarder.isActive)
-
- assertThrows<IllegalStateException> { forwarder.startConsumer(consumer) }
-
- forwarder.cancel()
- assertFalse(forwarder.isActive)
-
- forwarder.close()
- assertFalse(forwarder.isActive)
- }
-
- @Test
- fun testCancelPendingDelegate() = runBlockingSimulation {
- val forwarder = SimResourceForwarder()
-
- val consumer = mockk<SimResourceConsumer>(relaxUnitFun = true)
- every { consumer.onNext(any()) } returns SimResourceCommand.Exit
-
- forwarder.startConsumer(consumer)
- forwarder.cancel()
-
- verify(exactly = 0) { consumer.onEvent(any(), SimResourceEvent.Exit) }
- }
-
- @Test
- fun testCancelStartedDelegate() = runBlockingSimulation {
- val forwarder = SimResourceForwarder()
- val scheduler = SimResourceInterpreterImpl(coroutineContext, clock)
- val source = SimResourceSource(2000.0, scheduler)
-
- val consumer = mockk<SimResourceConsumer>(relaxUnitFun = true)
- every { consumer.onNext(any()) } returns SimResourceCommand.Idle(10)
-
- source.startConsumer(forwarder)
- yield()
- forwarder.startConsumer(consumer)
- yield()
- forwarder.cancel()
-
- verify(exactly = 1) { consumer.onEvent(any(), SimResourceEvent.Start) }
- verify(exactly = 1) { consumer.onEvent(any(), SimResourceEvent.Exit) }
- }
-
- @Test
- fun testCancelPropagation() = runBlockingSimulation {
- val forwarder = SimResourceForwarder()
- val scheduler = SimResourceInterpreterImpl(coroutineContext, clock)
- val source = SimResourceSource(2000.0, scheduler)
-
- val consumer = mockk<SimResourceConsumer>(relaxUnitFun = true)
- every { consumer.onNext(any()) } returns SimResourceCommand.Idle(10)
-
- source.startConsumer(forwarder)
- yield()
- forwarder.startConsumer(consumer)
- yield()
- source.cancel()
-
- verify(exactly = 1) { consumer.onEvent(any(), SimResourceEvent.Start) }
- verify(exactly = 1) { consumer.onEvent(any(), SimResourceEvent.Exit) }
- }
-
- @Test
- fun testExitPropagation() = runBlockingSimulation {
- val forwarder = SimResourceForwarder(isCoupled = true)
- val scheduler = SimResourceInterpreterImpl(coroutineContext, clock)
- val source = SimResourceSource(2000.0, scheduler)
-
- val consumer = mockk<SimResourceConsumer>(relaxUnitFun = true)
- every { consumer.onNext(any()) } returns SimResourceCommand.Exit
-
- source.startConsumer(forwarder)
- forwarder.consume(consumer)
- yield()
-
- assertFalse(forwarder.isActive)
- }
-
- @Test
- fun testAdjustCapacity() = runBlockingSimulation {
- val forwarder = SimResourceForwarder()
- val scheduler = SimResourceInterpreterImpl(coroutineContext, clock)
- val source = SimResourceSource(1.0, scheduler)
-
- val consumer = spyk(SimWorkConsumer(2.0, 1.0))
- source.startConsumer(forwarder)
-
- coroutineScope {
- launch { forwarder.consume(consumer) }
- delay(1000)
- source.capacity = 0.5
- }
-
- assertEquals(3000, clock.millis())
- verify(exactly = 1) { consumer.onEvent(any(), SimResourceEvent.Capacity) }
- }
-
- @Test
- fun testTransformExit() = runBlockingSimulation {
- val forwarder = SimResourceTransformer { _, _ -> SimResourceCommand.Exit }
- val scheduler = SimResourceInterpreterImpl(coroutineContext, clock)
- val source = SimResourceSource(1.0, scheduler)
-
- val consumer = spyk(SimWorkConsumer(2.0, 1.0))
- source.startConsumer(forwarder)
- forwarder.consume(consumer)
-
- assertEquals(0, clock.millis())
- verify(exactly = 1) { consumer.onNext(any()) }
- }
-
- @Test
- fun testCounters() = runBlockingSimulation {
- val forwarder = SimResourceForwarder()
- val scheduler = SimResourceInterpreterImpl(coroutineContext, clock)
- val source = SimResourceSource(1.0, scheduler)
-
- val consumer = SimWorkConsumer(2.0, 1.0)
- source.startConsumer(forwarder)
-
- forwarder.consume(consumer)
-
- assertEquals(source.counters.actual, forwarder.counters.actual) { "Actual work" }
- assertEquals(source.counters.demand, forwarder.counters.demand) { "Work demand" }
- assertEquals(source.counters.overcommit, forwarder.counters.overcommit) { "Overcommitted work" }
- assertEquals(2000, clock.millis())
- }
-}
diff --git a/opendc-web/opendc-web-runner/src/main/kotlin/org/opendc/web/runner/Main.kt b/opendc-web/opendc-web-runner/src/main/kotlin/org/opendc/web/runner/Main.kt
index 96b300d7..59308e11 100644
--- a/opendc-web/opendc-web-runner/src/main/kotlin/org/opendc/web/runner/Main.kt
+++ b/opendc-web/opendc-web-runner/src/main/kotlin/org/opendc/web/runner/Main.kt
@@ -123,7 +123,7 @@ class RunnerCli : CliktCommand(name = "runner") {
.default(60L * 3) // Experiment may run for a maximum of three minutes
/**
- * Run a single scenario.
+ * Converge a single scenario.
*/
private suspend fun runScenario(portfolio: ClientPortfolio, scenario: Scenario, topology: Topology): List<WebComputeMetricExporter.Result> {
val id = scenario.id
@@ -158,7 +158,7 @@ class RunnerCli : CliktCommand(name = "runner") {
}
/**
- * Run a single repeat.
+ * Converge a single repeat.
*/
private suspend fun runRepeat(
scenario: Scenario,
@@ -199,7 +199,7 @@ class RunnerCli : CliktCommand(name = "runner") {
try {
// Instantiate the topology onto the simulator
simulator.apply(topology)
- // Run workload trace
+ // Converge workload trace
simulator.run(workload.resolve(workloadLoader, seeder), seeder.nextLong())
} finally {
simulator.close()
diff --git a/opendc-workflow/opendc-workflow-service/src/test/kotlin/org/opendc/workflow/service/WorkflowServiceTest.kt b/opendc-workflow/opendc-workflow-service/src/test/kotlin/org/opendc/workflow/service/WorkflowServiceTest.kt
index 728dfd99..04f54e58 100644
--- a/opendc-workflow/opendc-workflow-service/src/test/kotlin/org/opendc/workflow/service/WorkflowServiceTest.kt
+++ b/opendc-workflow/opendc-workflow-service/src/test/kotlin/org/opendc/workflow/service/WorkflowServiceTest.kt
@@ -42,7 +42,7 @@ 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.core.runBlockingSimulation
-import org.opendc.simulator.resources.SimResourceInterpreter
+import org.opendc.simulator.flow.FlowEngine
import org.opendc.telemetry.sdk.toOtelClock
import org.opendc.trace.Trace
import org.opendc.workflow.service.internal.WorkflowServiceImpl
@@ -70,7 +70,7 @@ internal class WorkflowServiceTest {
.setClock(clock.toOtelClock())
.build()
- val interpreter = SimResourceInterpreter(coroutineContext, clock)
+ val interpreter = FlowEngine(coroutineContext, clock)
val machineModel = createMachineModel()
val hvProvider = SimSpaceSharedHypervisorProvider()
val hosts = List(4) { id ->
@@ -126,7 +126,7 @@ internal class WorkflowServiceTest {
{ assertEquals(metrics.jobsSubmitted, metrics.jobsFinished, "Not all started jobs finished") },
{ assertEquals(0, metrics.tasksActive, "Not all started tasks finished") },
{ assertEquals(metrics.tasksSubmitted, metrics.tasksFinished, "Not all started tasks finished") },
- { assertEquals(33213237L, clock.millis()) }
+ { assertEquals(33213236L, clock.millis()) }
)
}
diff --git a/settings.gradle.kts b/settings.gradle.kts
index 587f1cb2..d9b3d940 100644
--- a/settings.gradle.kts
+++ b/settings.gradle.kts
@@ -38,7 +38,7 @@ include(":opendc-web:opendc-web-api")
include(":opendc-web:opendc-web-ui")
include(":opendc-web:opendc-web-runner")
include(":opendc-simulator:opendc-simulator-core")
-include(":opendc-simulator:opendc-simulator-resources")
+include(":opendc-simulator:opendc-simulator-flow")
include(":opendc-simulator:opendc-simulator-power")
include(":opendc-simulator:opendc-simulator-network")
include(":opendc-simulator:opendc-simulator-compute")