summaryrefslogtreecommitdiff
path: root/simulator
diff options
context:
space:
mode:
authorFabian Mastenbroek <mail.fabianm@gmail.com>2020-10-02 00:38:44 +0200
committerFabian Mastenbroek <mail.fabianm@gmail.com>2020-10-02 20:57:35 +0200
commit76c5d27618181c4ec9cf86085c65b5e69f5f9109 (patch)
treec11446962064b610f0f8373cfdf73debdfc3d9d9 /simulator
parent0119ca3b2e05b06f9646149c2d7bfe3d4b57c380 (diff)
Add opendc-simulator-compute module
This change adds an opendc-simulator-compute module which contains interfaces related to simulating compute workloads. For future changes, we intend to decouple the simulation part from the opendc-compute module.
Diffstat (limited to 'simulator')
-rw-r--r--simulator/opendc-compute/build.gradle.kts2
-rw-r--r--simulator/opendc-experiments/opendc-experiments-sc18/build.gradle.kts2
-rw-r--r--simulator/opendc-experiments/opendc-experiments-sc20/build.gradle.kts2
-rw-r--r--simulator/opendc-runner-web/build.gradle.kts2
-rw-r--r--simulator/opendc-runner-web/src/main/kotlin/org/opendc/runner/web/Main.kt1
-rw-r--r--simulator/opendc-simulator/build.gradle.kts32
-rw-r--r--simulator/opendc-simulator/opendc-simulator-compute/build.gradle.kts35
-rw-r--r--simulator/opendc-simulator/opendc-simulator-compute/src/main/kotlin/org/opendc/simulator/compute/SimExecutionContext.kt155
-rw-r--r--simulator/opendc-simulator/opendc-simulator-compute/src/main/kotlin/org/opendc/simulator/compute/SimMachine.kt266
-rw-r--r--simulator/opendc-simulator/opendc-simulator-compute/src/main/kotlin/org/opendc/simulator/compute/SimMachineModel.kt34
-rw-r--r--simulator/opendc-simulator/opendc-simulator-compute/src/main/kotlin/org/opendc/simulator/compute/model/MemoryUnit.kt38
-rw-r--r--simulator/opendc-simulator/opendc-simulator-compute/src/main/kotlin/org/opendc/simulator/compute/model/ProcessingNode.kt38
-rw-r--r--simulator/opendc-simulator/opendc-simulator-compute/src/main/kotlin/org/opendc/simulator/compute/model/ProcessingUnit.kt36
-rw-r--r--simulator/opendc-simulator/opendc-simulator-compute/src/main/kotlin/org/opendc/simulator/compute/workload/SimFlopsWorkload.kt57
-rw-r--r--simulator/opendc-simulator/opendc-simulator-compute/src/main/kotlin/org/opendc/simulator/compute/workload/SimTraceWorkload.kt53
-rw-r--r--simulator/opendc-simulator/opendc-simulator-compute/src/main/kotlin/org/opendc/simulator/compute/workload/SimWorkload.kt38
-rw-r--r--simulator/opendc-simulator/opendc-simulator-compute/src/test/kotlin/org/opendc/simulator/compute/SimMachineTest.kt83
-rw-r--r--simulator/opendc-simulator/opendc-simulator-compute/src/test/kotlin/org/opendc/simulator/compute/workload/SimFlopsWorkloadTest.kt73
-rw-r--r--simulator/opendc-simulator/opendc-simulator-core/build.gradle.kts32
-rw-r--r--simulator/opendc-simulator/opendc-simulator-core/src/main/kotlin/org/opendc/simulator/utils/DelayControllerClockAdapter.kt (renamed from simulator/opendc-simulator/src/main/kotlin/org/opendc/simulator/utils/DelayControllerClockAdapter.kt)0
-rw-r--r--simulator/opendc-workflows/build.gradle.kts2
-rw-r--r--simulator/settings.gradle.kts3
22 files changed, 945 insertions, 39 deletions
diff --git a/simulator/opendc-compute/build.gradle.kts b/simulator/opendc-compute/build.gradle.kts
index dfdd6f35..ea82faa5 100644
--- a/simulator/opendc-compute/build.gradle.kts
+++ b/simulator/opendc-compute/build.gradle.kts
@@ -32,7 +32,7 @@ dependencies {
implementation(project(":opendc-utils"))
implementation("io.github.microutils:kotlin-logging:1.7.9")
- testImplementation(project(":opendc-simulator"))
+ testImplementation(project(":opendc-simulator:opendc-simulator-core"))
testRuntimeOnly("org.slf4j:slf4j-simple:${Library.SLF4J}")
testImplementation("org.junit.jupiter:junit-jupiter-api:${Library.JUNIT_JUPITER}")
testRuntimeOnly("org.junit.jupiter:junit-jupiter-engine:${Library.JUNIT_JUPITER}")
diff --git a/simulator/opendc-experiments/opendc-experiments-sc18/build.gradle.kts b/simulator/opendc-experiments/opendc-experiments-sc18/build.gradle.kts
index 393fd0d0..b12b1ae3 100644
--- a/simulator/opendc-experiments/opendc-experiments-sc18/build.gradle.kts
+++ b/simulator/opendc-experiments/opendc-experiments-sc18/build.gradle.kts
@@ -36,7 +36,7 @@ dependencies {
api(project(":opendc-core"))
implementation(project(":opendc-format"))
implementation(project(":opendc-workflows"))
- implementation(project(":opendc-simulator"))
+ implementation(project(":opendc-simulator:opendc-simulator-core"))
implementation(kotlin("stdlib"))
testImplementation("org.junit.jupiter:junit-jupiter-api:${Library.JUNIT_JUPITER}")
diff --git a/simulator/opendc-experiments/opendc-experiments-sc20/build.gradle.kts b/simulator/opendc-experiments/opendc-experiments-sc20/build.gradle.kts
index 4bfe21d2..1a7c2f78 100644
--- a/simulator/opendc-experiments/opendc-experiments-sc20/build.gradle.kts
+++ b/simulator/opendc-experiments/opendc-experiments-sc20/build.gradle.kts
@@ -36,7 +36,7 @@ application {
dependencies {
api(project(":opendc-core"))
implementation(project(":opendc-format"))
- implementation(project(":opendc-simulator"))
+ implementation(project(":opendc-simulator:opendc-simulator-core"))
implementation("com.github.ajalt:clikt:2.6.0")
implementation("me.tongfei:progressbar:0.8.1")
diff --git a/simulator/opendc-runner-web/build.gradle.kts b/simulator/opendc-runner-web/build.gradle.kts
index 7e81347c..594e8197 100644
--- a/simulator/opendc-runner-web/build.gradle.kts
+++ b/simulator/opendc-runner-web/build.gradle.kts
@@ -37,7 +37,7 @@ dependencies {
implementation(project(":opendc-compute"))
implementation(project(":opendc-format"))
implementation(project(":opendc-experiments:opendc-experiments-sc20"))
- implementation(project(":opendc-simulator"))
+ implementation(project(":opendc-simulator:opendc-simulator-core"))
implementation("com.github.ajalt:clikt:2.8.0")
implementation("io.github.microutils:kotlin-logging:1.7.10")
diff --git a/simulator/opendc-runner-web/src/main/kotlin/org/opendc/runner/web/Main.kt b/simulator/opendc-runner-web/src/main/kotlin/org/opendc/runner/web/Main.kt
index 67ec046a..2d4272ab 100644
--- a/simulator/opendc-runner-web/src/main/kotlin/org/opendc/runner/web/Main.kt
+++ b/simulator/opendc-runner-web/src/main/kotlin/org/opendc/runner/web/Main.kt
@@ -50,7 +50,6 @@ import org.opendc.experiments.sc20.trace.Sc20RawParquetTraceReader
import org.opendc.format.trace.sc20.Sc20PerformanceInterferenceReader
import org.opendc.simulator.utils.DelayControllerClockAdapter
import java.io.File
-import java.util.*
import kotlin.random.Random
private val logger = KotlinLogging.logger {}
diff --git a/simulator/opendc-simulator/build.gradle.kts b/simulator/opendc-simulator/build.gradle.kts
index a740dcf3..e69de29b 100644
--- a/simulator/opendc-simulator/build.gradle.kts
+++ b/simulator/opendc-simulator/build.gradle.kts
@@ -1,32 +0,0 @@
-/*
- * Copyright (c) 2020 AtLarge Research
- *
- * Permission is hereby granted, free of charge, to any person obtaining a copy
- * of this software and associated documentation files (the "Software"), to deal
- * in the Software without restriction, including without limitation the rights
- * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
- * copies of the Software, and to permit persons to whom the Software is
- * furnished to do so, subject to the following conditions:
- *
- * The above copyright notice and this permission notice shall be included in all
- * copies or substantial portions of the Software.
- *
- * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
- * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
- * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
- * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
- * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
- * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
- * SOFTWARE.
- */
-
-description = "Simulation-specific code for use in OpenDC"
-
-/* Build configuration */
-plugins {
- `kotlin-library-convention`
-}
-
-dependencies {
- api("org.jetbrains.kotlinx:kotlinx-coroutines-test:${Library.KOTLINX_COROUTINES}")
-}
diff --git a/simulator/opendc-simulator/opendc-simulator-compute/build.gradle.kts b/simulator/opendc-simulator/opendc-simulator-compute/build.gradle.kts
new file mode 100644
index 00000000..cd7e5706
--- /dev/null
+++ b/simulator/opendc-simulator/opendc-simulator-compute/build.gradle.kts
@@ -0,0 +1,35 @@
+/*
+ * Copyright (c) 2020 AtLarge Research
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
+ * of this software and associated documentation files (the "Software"), to deal
+ * in the Software without restriction, including without limitation the rights
+ * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+ * copies of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included in all
+ * copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
+ * SOFTWARE.
+ */
+
+description = "Library for simulation of cloud computing components"
+
+plugins {
+ `kotlin-library-convention`
+}
+
+dependencies {
+ api(project(":opendc-simulator:opendc-simulator-core"))
+
+ testImplementation("org.junit.jupiter:junit-jupiter-api:${Library.JUNIT_JUPITER}")
+ testRuntimeOnly("org.junit.jupiter:junit-jupiter-engine:${Library.JUNIT_JUPITER}")
+ testImplementation("org.junit.platform:junit-platform-launcher:${Library.JUNIT_PLATFORM}")
+}
diff --git a/simulator/opendc-simulator/opendc-simulator-compute/src/main/kotlin/org/opendc/simulator/compute/SimExecutionContext.kt b/simulator/opendc-simulator/opendc-simulator-compute/src/main/kotlin/org/opendc/simulator/compute/SimExecutionContext.kt
new file mode 100644
index 00000000..5801fcd5
--- /dev/null
+++ b/simulator/opendc-simulator/opendc-simulator-compute/src/main/kotlin/org/opendc/simulator/compute/SimExecutionContext.kt
@@ -0,0 +1,155 @@
+/*
+ * Copyright (c) 2020 AtLarge Research
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
+ * of this software and associated documentation files (the "Software"), to deal
+ * in the Software without restriction, including without limitation the rights
+ * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+ * copies of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included in all
+ * copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
+ * SOFTWARE.
+ */
+
+package org.opendc.simulator.compute
+
+import kotlinx.coroutines.selects.SelectClause0
+import kotlinx.coroutines.selects.select
+import java.time.Clock
+
+/**
+ * A simulated execution context in which a bootable image runs. This interface represents the
+ * firmware interface between the running image (e.g. operating system) and the physical or virtual firmware on
+ * which the image runs.
+ */
+public interface SimExecutionContext {
+ /**
+ * The virtual clock tracking simulation time.
+ */
+ public val clock: Clock
+
+ /**
+ * The machine model of the machine that is running the image.
+ */
+ public val machine: SimMachineModel
+
+ /**
+ * Ask the processor cores to run the specified [slice] and suspend execution until the trigger condition is met as
+ * specified by [triggerMode].
+ *
+ * After the method returns, [Slice.burst] will contain the remaining burst length for each of the cores (which
+ * may be zero). These changes may happen anytime during execution of this method and callers should not rely on
+ * the timing of this change.
+ *
+ * @param slice The representation of work to run on the processors.
+ * @param triggerMode The trigger condition to resume execution.
+ */
+ public suspend fun run(slice: Slice, triggerMode: TriggerMode = TriggerMode.FIRST): Unit =
+ select { onRun(slice, triggerMode).invoke {} }
+
+ /**
+ * Ask the processors cores to run the specified [batch] of work slices and suspend execution until the trigger
+ * condition is met as specified by [triggerMode].
+ *
+ * After the method returns, [Slice.burst] will contain the remaining burst length for each of the cores (which
+ * may be zero). These changes may happen anytime during execution of this method and callers should not rely on
+ * the timing of this change.
+ *
+ * In case slices in the batch do not finish processing before their deadline, [merge] is called to merge these
+ * slices with the next slice to be executed.
+ *
+ * @param batch The batch of work to run on the processors.
+ * @param triggerMode The trigger condition to resume execution.
+ * @param merge The merge function for consecutive slices in case the last slice was not completed within its
+ * deadline.
+ */
+ public suspend fun run(
+ batch: Sequence<Slice>,
+ triggerMode: TriggerMode = TriggerMode.FIRST,
+ merge: (Slice, Slice) -> Slice = { _, r -> r }
+ ): Unit = select { onRun(batch, triggerMode, merge).invoke {} }
+
+ /**
+ * Ask the processor cores to run the specified [slice] and select when the trigger condition is met as specified
+ * by [triggerMode].
+ *
+ * After the method returns, [Slice.burst] will contain the remaining burst length for each of the cores (which
+ * may be zero). These changes may happen anytime during execution of this method and callers should not rely on
+ * the timing of this change.
+ *
+ * @param slice The representation of work to request from the processors.
+ * @param triggerMode The trigger condition to resume execution.
+ */
+ public fun onRun(slice: Slice, triggerMode: TriggerMode = TriggerMode.FIRST): SelectClause0 =
+ onRun(sequenceOf(slice), triggerMode)
+
+ /**
+ * Ask the processors cores to run the specified [batch] of work slices and select when the trigger condition is met
+ * as specified by [triggerMode].
+ *
+ * After the method returns, [Slice.burst] will contain the remaining burst length for each of the cores (which
+ * may be zero). These changes may happen anytime during execution of this method and callers should not rely on
+ * the timing of this change.
+ *
+ * In case slices in the batch do not finish processing before their deadline, [merge] is called to merge these
+ * slices with the next slice to be executed.
+ *
+ * @param batch The batch of work to run on the processors.
+ * @param triggerMode The trigger condition to resume execution during the **last** slice.
+ * @param merge The merge function for consecutive slices in case the last slice was not completed within its
+ * deadline.
+ */
+ public fun onRun(
+ batch: Sequence<Slice>,
+ triggerMode: TriggerMode = TriggerMode.FIRST,
+ merge: (Slice, Slice) -> Slice = { _, r -> r }
+ ): SelectClause0
+
+ /**
+ * A request to the host machine for a slice of CPU time from the processor cores.
+ *
+ * Both [burst] and [limit] must be of the same size and in any other case the method will throw an
+ * [IllegalArgumentException].
+ *
+ *
+ * @param burst The burst time to request from each of the processor cores.
+ * @param limit The maximum usage in terms of MHz that the processing core may use while running the burst.
+ * @param deadline The instant at which this slice needs to be fulfilled.
+ */
+ public class Slice(public val burst: LongArray, public val limit: DoubleArray, public val deadline: Long) {
+ init {
+ require(burst.size == limit.size) { "Incompatible array dimensions" }
+ }
+ }
+
+ /**
+ * The modes for triggering a machine exit from the machine.
+ */
+ public enum class TriggerMode {
+ /**
+ * A machine exit occurs when either the first processor finishes processing a **non-zero** burst or the
+ * deadline is reached.
+ */
+ FIRST,
+
+ /**
+ * A machine exit occurs when either the last processor finishes processing a **non-zero** burst or the deadline
+ * is reached.
+ */
+ LAST,
+
+ /**
+ * A machine exit occurs only when the deadline is reached.
+ */
+ DEADLINE
+ }
+}
diff --git a/simulator/opendc-simulator/opendc-simulator-compute/src/main/kotlin/org/opendc/simulator/compute/SimMachine.kt b/simulator/opendc-simulator/opendc-simulator-compute/src/main/kotlin/org/opendc/simulator/compute/SimMachine.kt
new file mode 100644
index 00000000..df74a5f1
--- /dev/null
+++ b/simulator/opendc-simulator/opendc-simulator-compute/src/main/kotlin/org/opendc/simulator/compute/SimMachine.kt
@@ -0,0 +1,266 @@
+/*
+ * Copyright (c) 2020 AtLarge Research
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
+ * of this software and associated documentation files (the "Software"), to deal
+ * in the Software without restriction, including without limitation the rights
+ * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+ * copies of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included in all
+ * copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
+ * SOFTWARE.
+ */
+
+package org.opendc.simulator.compute
+
+import kotlinx.coroutines.*
+import kotlinx.coroutines.flow.MutableStateFlow
+import kotlinx.coroutines.flow.StateFlow
+import kotlinx.coroutines.intrinsics.startCoroutineCancellable
+import kotlinx.coroutines.selects.SelectClause0
+import kotlinx.coroutines.selects.SelectInstance
+import org.opendc.simulator.compute.workload.SimWorkload
+import java.lang.Runnable
+import java.time.Clock
+import kotlin.coroutines.ContinuationInterceptor
+import kotlin.math.ceil
+import kotlin.math.max
+import kotlin.math.min
+
+/**
+ * A simulated bare-metal machine that is able to run a single workload.
+ *
+ * @param coroutineScope The [CoroutineScope] to run the simulated workload in.
+ * @param clock The virtual clock to track the simulation time.
+ * @param model The machine model to simulate.
+ */
+@OptIn(ExperimentalCoroutinesApi::class, InternalCoroutinesApi::class)
+public class SimMachine(
+ private val coroutineScope: CoroutineScope,
+ private val clock: Clock,
+ public val model: SimMachineModel
+) {
+ /**
+ * A [StateFlow] representing the CPU usage of the simulated machine.
+ */
+ public val usage: StateFlow<Double>
+ get() = usageState
+
+ /**
+ * Run the specified [SimWorkload] on this machine and suspend execution util the workload has finished.
+ */
+ public suspend fun run(workload: SimWorkload) {
+ workload.run(ctx)
+ }
+
+ /**
+ * The execution context in which the workload runs.
+ */
+ private val ctx = object : SimExecutionContext {
+ override val machine: SimMachineModel
+ get() = this@SimMachine.model
+
+ override val clock: Clock
+ get() = this@SimMachine.clock
+
+ override fun onRun(
+ batch: Sequence<SimExecutionContext.Slice>,
+ triggerMode: SimExecutionContext.TriggerMode,
+ merge: (SimExecutionContext.Slice, SimExecutionContext.Slice) -> SimExecutionContext.Slice
+ ): SelectClause0 {
+ return object : SelectClause0 {
+ @InternalCoroutinesApi
+ override fun <R> registerSelectClause0(select: SelectInstance<R>, block: suspend () -> R) {
+ // Do not reset the usage state: we will set it ourselves
+ usageFlush?.dispose()
+ usageFlush = null
+
+ val queue = batch.iterator()
+ var start = Long.MIN_VALUE
+ var currentWork: SliceWork? = null
+ var currentDisposable: DisposableHandle? = null
+
+ fun schedule(slice: SimExecutionContext.Slice) {
+ start = clock.millis()
+
+ val isLastSlice = !queue.hasNext()
+ val work = SliceWork(slice)
+ val candidateDuration = when (triggerMode) {
+ SimExecutionContext.TriggerMode.FIRST -> work.minExit
+ SimExecutionContext.TriggerMode.LAST -> work.maxExit
+ SimExecutionContext.TriggerMode.DEADLINE -> slice.deadline - start
+ }
+
+ // Check whether the deadline is exceeded during the run of the slice.
+ val duration = min(candidateDuration, slice.deadline - start)
+
+ val action = Runnable {
+ currentWork = null
+
+ // Flush all the work that was performed
+ val hasFinished = work.stop(duration)
+
+ if (!isLastSlice) {
+ val candidateSlice = queue.next()
+ val nextSlice =
+ // If our previous slice exceeds its deadline, merge it with the next candidate slice
+ if (hasFinished)
+ candidateSlice
+ else
+ merge(candidateSlice, slice)
+ schedule(nextSlice)
+ } else if (select.trySelect()) {
+ block.startCoroutineCancellable(select.completion)
+ }
+ }
+
+ // Schedule the flush after the entire slice has finished
+ currentDisposable = delay.invokeOnTimeout(duration, action)
+
+ // Start the slice work
+ currentWork = work
+ work.start()
+ }
+
+ // Schedule the first work
+ if (queue.hasNext()) {
+ schedule(queue.next())
+
+ // A DisposableHandle to flush the work in case the call is cancelled
+ val disposable = DisposableHandle {
+ val end = clock.millis()
+ val duration = end - start
+
+ currentWork?.stop(duration)
+ currentDisposable?.dispose()
+
+ // Schedule reset the usage of the machine since the call is returning
+ usageFlush = delay.invokeOnTimeout(1) {
+ usageState.value = 0.0
+ usageFlush = null
+ }
+ }
+
+ select.disposeOnSelect(disposable)
+ } else if (select.trySelect()) {
+ // No work has been given: select immediately
+ block.startCoroutineCancellable(select.completion)
+ }
+ }
+ }
+ }
+ }
+
+ /**
+ * The [MutableStateFlow] containing the load of the server.
+ */
+ private val usageState = MutableStateFlow(0.0)
+
+ /**
+ * A disposable to prevent resetting the usage state for subsequent calls to onRun.
+ */
+ private var usageFlush: DisposableHandle? = null
+
+ /**
+ * Cache the [Delay] instance for timing.
+ *
+ * XXX We need to cache this before the call to [onRun] since doing this in [onRun] is too heavy.
+ * XXX Note however that this is an ugly hack which may break in the future.
+ */
+ @OptIn(InternalCoroutinesApi::class)
+ private val delay = coroutineScope.coroutineContext[ContinuationInterceptor] as Delay
+
+ /**
+ * A slice to be processed.
+ */
+ private inner class SliceWork(val slice: SimExecutionContext.Slice) {
+ /**
+ * The duration after which the first processor finishes processing this slice.
+ */
+ val minExit: Long
+
+ /**
+ * The duration after which the last processor finishes processing this slice.
+ */
+ val maxExit: Long
+
+ /**
+ * A flag to indicate that the slice will exceed the deadline.
+ */
+ val exceedsDeadline: Boolean
+ get() = slice.deadline < maxExit
+
+ /**
+ * The total amount of CPU usage.
+ */
+ val totalUsage: Double
+
+ /**
+ * A flag to indicate that this slice is empty.
+ */
+ val isEmpty: Boolean
+
+ init {
+ var totalUsage = 0.0
+ var minExit = Long.MAX_VALUE
+ var maxExit = 0L
+ var nonEmpty = false
+
+ // Determine the duration of the first/last CPU to finish
+ for (i in 0 until min(model.cpus.size, slice.burst.size)) {
+ val cpu = model.cpus[i]
+ val usage = min(slice.limit[i], cpu.frequency)
+ val cpuDuration = ceil(slice.burst[i] / usage * 1000).toLong() // Convert from seconds to milliseconds
+
+ totalUsage += usage / cpu.frequency
+
+ if (cpuDuration != 0L) { // We only wait for processor cores with a non-zero burst
+ minExit = min(minExit, cpuDuration)
+ maxExit = max(maxExit, cpuDuration)
+ nonEmpty = true
+ }
+ }
+
+ this.isEmpty = !nonEmpty
+ this.totalUsage = totalUsage
+ this.minExit = minExit
+ this.maxExit = maxExit
+ }
+
+ /**
+ * Indicate that the work on the slice has started.
+ */
+ fun start() {
+ usageState.value = totalUsage / model.cpus.size
+ }
+
+ /**
+ * Flush the work performed on the slice.
+ */
+ fun stop(duration: Long): Boolean {
+ var hasFinished = true
+
+ for (i in 0 until min(model.cpus.size, slice.burst.size)) {
+ val usage = min(slice.limit[i], model.cpus[i].frequency)
+ val granted = ceil(duration / 1000.0 * usage).toLong()
+ val res = max(0, slice.burst[i] - granted)
+ slice.burst[i] = res
+
+ if (res != 0L) {
+ hasFinished = false
+ }
+ }
+
+ return hasFinished
+ }
+ }
+}
diff --git a/simulator/opendc-simulator/opendc-simulator-compute/src/main/kotlin/org/opendc/simulator/compute/SimMachineModel.kt b/simulator/opendc-simulator/opendc-simulator-compute/src/main/kotlin/org/opendc/simulator/compute/SimMachineModel.kt
new file mode 100644
index 00000000..c2988b11
--- /dev/null
+++ b/simulator/opendc-simulator/opendc-simulator-compute/src/main/kotlin/org/opendc/simulator/compute/SimMachineModel.kt
@@ -0,0 +1,34 @@
+/*
+ * Copyright (c) 2020 AtLarge Research
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
+ * of this software and associated documentation files (the "Software"), to deal
+ * in the Software without restriction, including without limitation the rights
+ * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+ * copies of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included in all
+ * copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
+ * SOFTWARE.
+ */
+
+package org.opendc.simulator.compute
+
+import org.opendc.simulator.compute.model.MemoryUnit
+import org.opendc.simulator.compute.model.ProcessingUnit
+
+/**
+ * A description of the physical or virtual machine on which a bootable image runs.
+ *
+ * @property cpus The list of processing units available to the image.
+ * @property memory The list of memory units available to the image.
+ */
+public data class SimMachineModel(public val cpus: List<ProcessingUnit>, public val memory: List<MemoryUnit>)
diff --git a/simulator/opendc-simulator/opendc-simulator-compute/src/main/kotlin/org/opendc/simulator/compute/model/MemoryUnit.kt b/simulator/opendc-simulator/opendc-simulator-compute/src/main/kotlin/org/opendc/simulator/compute/model/MemoryUnit.kt
new file mode 100644
index 00000000..bcbde5b1
--- /dev/null
+++ b/simulator/opendc-simulator/opendc-simulator-compute/src/main/kotlin/org/opendc/simulator/compute/model/MemoryUnit.kt
@@ -0,0 +1,38 @@
+/*
+ * Copyright (c) 2020 AtLarge Research
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
+ * of this software and associated documentation files (the "Software"), to deal
+ * in the Software without restriction, including without limitation the rights
+ * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+ * copies of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included in all
+ * copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
+ * SOFTWARE.
+ */
+
+package org.opendc.simulator.compute.model
+
+/**
+ * A memory unit of a compute resource, either virtual or physical.
+ *
+ * @property vendor The vendor string of the memory.
+ * @property modelName The name of the memory model.
+ * @property speed The access speed of the memory in MHz.
+ * @property size The size of the memory unit in MBs.
+ */
+public data class MemoryUnit(
+ public val vendor: String,
+ public val modelName: String,
+ public val speed: Double,
+ public val size: Long
+)
diff --git a/simulator/opendc-simulator/opendc-simulator-compute/src/main/kotlin/org/opendc/simulator/compute/model/ProcessingNode.kt b/simulator/opendc-simulator/opendc-simulator-compute/src/main/kotlin/org/opendc/simulator/compute/model/ProcessingNode.kt
new file mode 100644
index 00000000..58ed816c
--- /dev/null
+++ b/simulator/opendc-simulator/opendc-simulator-compute/src/main/kotlin/org/opendc/simulator/compute/model/ProcessingNode.kt
@@ -0,0 +1,38 @@
+/*
+ * Copyright (c) 2020 AtLarge Research
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
+ * of this software and associated documentation files (the "Software"), to deal
+ * in the Software without restriction, including without limitation the rights
+ * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+ * copies of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included in all
+ * copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
+ * SOFTWARE.
+ */
+
+package org.opendc.simulator.compute.model
+
+/**
+ * A processing node/package/socket containing possibly several CPU cores.
+ *
+ * @property vendor The vendor string of the processor node.
+ * @property modelName The name of the processor node.
+ * @property arch The micro-architecture of the processor node.
+ * @property coreCount The number of logical CPUs in the processor node.
+ */
+public data class ProcessingNode(
+ public val vendor: String,
+ public val arch: String,
+ public val modelName: String,
+ public val coreCount: Int
+)
diff --git a/simulator/opendc-simulator/opendc-simulator-compute/src/main/kotlin/org/opendc/simulator/compute/model/ProcessingUnit.kt b/simulator/opendc-simulator/opendc-simulator-compute/src/main/kotlin/org/opendc/simulator/compute/model/ProcessingUnit.kt
new file mode 100644
index 00000000..415e95e6
--- /dev/null
+++ b/simulator/opendc-simulator/opendc-simulator-compute/src/main/kotlin/org/opendc/simulator/compute/model/ProcessingUnit.kt
@@ -0,0 +1,36 @@
+/*
+ * Copyright (c) 2020 AtLarge Research
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
+ * of this software and associated documentation files (the "Software"), to deal
+ * in the Software without restriction, including without limitation the rights
+ * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+ * copies of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included in all
+ * copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
+ * SOFTWARE.
+ */
+
+package org.opendc.simulator.compute.model
+
+/**
+ * A single logical compute unit of processor node, either virtual or physical.
+ *
+ * @property node The processing node containing the CPU core.
+ * @property id The identifier of the CPU core within the processing node.
+ * @property frequency The clock rate of the CPU in MHz.
+ */
+public data class ProcessingUnit(
+ public val node: ProcessingNode,
+ public val id: Int,
+ public val frequency: Double
+)
diff --git a/simulator/opendc-simulator/opendc-simulator-compute/src/main/kotlin/org/opendc/simulator/compute/workload/SimFlopsWorkload.kt b/simulator/opendc-simulator/opendc-simulator-compute/src/main/kotlin/org/opendc/simulator/compute/workload/SimFlopsWorkload.kt
new file mode 100644
index 00000000..918a78bd
--- /dev/null
+++ b/simulator/opendc-simulator/opendc-simulator-compute/src/main/kotlin/org/opendc/simulator/compute/workload/SimFlopsWorkload.kt
@@ -0,0 +1,57 @@
+/*
+ * Copyright (c) 2020 AtLarge Research
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
+ * of this software and associated documentation files (the "Software"), to deal
+ * in the Software without restriction, including without limitation the rights
+ * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+ * copies of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included in all
+ * copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
+ * SOFTWARE.
+ */
+
+package org.opendc.simulator.compute.workload
+
+import org.opendc.simulator.compute.SimExecutionContext
+import kotlin.math.min
+
+/**
+ * A [SimWorkload] that models applications performing a static number of floating point operations ([flops]) on
+ * a compute resource.
+ *
+ * @property flops The number of floating point operations to perform for this task in MFLOPs.
+ * @property cores The number of cores that the image is able to utilize.
+ * @property utilization A model of the CPU utilization of the application.
+ */
+public class SimFlopsWorkload(
+ public val flops: Long,
+ public val cores: Int,
+ public val utilization: Double = 0.8
+) : SimWorkload {
+ init {
+ require(flops >= 0) { "Negative number of flops" }
+ require(cores > 0) { "Negative number of cores or no cores" }
+ require(utilization > 0.0 && utilization <= 1.0) { "Utilization must be in (0, 1]" }
+ }
+
+ /**
+ * Execute the runtime behavior based on a number of floating point operations to execute.
+ */
+ override suspend fun run(ctx: SimExecutionContext) {
+ val cores = min(this.cores, ctx.machine.cpus.size)
+ val burst = LongArray(cores) { flops / cores }
+ val maxUsage = DoubleArray(cores) { i -> ctx.machine.cpus[i].frequency * utilization }
+
+ ctx.run(SimExecutionContext.Slice(burst, maxUsage, Long.MAX_VALUE), triggerMode = SimExecutionContext.TriggerMode.LAST)
+ }
+}
diff --git a/simulator/opendc-simulator/opendc-simulator-compute/src/main/kotlin/org/opendc/simulator/compute/workload/SimTraceWorkload.kt b/simulator/opendc-simulator/opendc-simulator-compute/src/main/kotlin/org/opendc/simulator/compute/workload/SimTraceWorkload.kt
new file mode 100644
index 00000000..e7eaa8bf
--- /dev/null
+++ b/simulator/opendc-simulator/opendc-simulator-compute/src/main/kotlin/org/opendc/simulator/compute/workload/SimTraceWorkload.kt
@@ -0,0 +1,53 @@
+/*
+ * Copyright (c) 2020 AtLarge Research
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
+ * of this software and associated documentation files (the "Software"), to deal
+ * in the Software without restriction, including without limitation the rights
+ * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+ * copies of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included in all
+ * copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
+ * SOFTWARE.
+ */
+
+package org.opendc.simulator.compute.workload
+
+import org.opendc.simulator.compute.SimExecutionContext
+import kotlin.math.min
+
+/**
+ * A [SimWorkload] that replays a workload trace consisting of multiple fragments, each indicating the resource
+ * consumption for some period of time.
+ */
+public class SimTraceWorkload(private val trace: Sequence<Fragment>) : SimWorkload {
+ override suspend fun run(ctx: SimExecutionContext) {
+ var offset = ctx.clock.millis()
+
+ val batch = trace.map { fragment ->
+ val cores = min(fragment.cores, ctx.machine.cpus.size)
+ val burst = LongArray(cores) { fragment.flops / cores }
+ val usage = DoubleArray(cores) { fragment.usage / cores }
+ offset += fragment.duration
+ SimExecutionContext.Slice(burst, usage, offset)
+ }
+
+ ctx.run(batch)
+ }
+
+ override fun toString(): String = "SimTraceWorkload"
+
+ /**
+ * A fragment of the workload.
+ */
+ public data class Fragment(val time: Long, val flops: Long, val duration: Long, val usage: Double, val cores: Int)
+}
diff --git a/simulator/opendc-simulator/opendc-simulator-compute/src/main/kotlin/org/opendc/simulator/compute/workload/SimWorkload.kt b/simulator/opendc-simulator/opendc-simulator-compute/src/main/kotlin/org/opendc/simulator/compute/workload/SimWorkload.kt
new file mode 100644
index 00000000..0ef4130e
--- /dev/null
+++ b/simulator/opendc-simulator/opendc-simulator-compute/src/main/kotlin/org/opendc/simulator/compute/workload/SimWorkload.kt
@@ -0,0 +1,38 @@
+/*
+ * Copyright (c) 2020 AtLarge Research
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
+ * of this software and associated documentation files (the "Software"), to deal
+ * in the Software without restriction, including without limitation the rights
+ * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+ * copies of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included in all
+ * copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
+ * SOFTWARE.
+ */
+
+package org.opendc.simulator.compute.workload
+
+import org.opendc.simulator.compute.SimExecutionContext
+
+/**
+ * A model that characterizes the runtime behavior of some particular workload.
+ */
+public interface SimWorkload {
+ /**
+ * Launch the workload in the specified [SimExecutionContext].
+ *
+ * This method should encapsulate and characterize the runtime behavior of the instance resulting from launching
+ * the workload on some machine, in terms of the resource consumption on the machine.
+ */
+ public suspend fun run(ctx: SimExecutionContext)
+}
diff --git a/simulator/opendc-simulator/opendc-simulator-compute/src/test/kotlin/org/opendc/simulator/compute/SimMachineTest.kt b/simulator/opendc-simulator/opendc-simulator-compute/src/test/kotlin/org/opendc/simulator/compute/SimMachineTest.kt
new file mode 100644
index 00000000..f6fb8e04
--- /dev/null
+++ b/simulator/opendc-simulator/opendc-simulator-compute/src/test/kotlin/org/opendc/simulator/compute/SimMachineTest.kt
@@ -0,0 +1,83 @@
+/*
+ * Copyright (c) 2020 AtLarge Research
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
+ * of this software and associated documentation files (the "Software"), to deal
+ * in the Software without restriction, including without limitation the rights
+ * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+ * copies of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included in all
+ * copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
+ * SOFTWARE.
+ */
+
+package org.opendc.simulator.compute
+
+import kotlinx.coroutines.*
+import kotlinx.coroutines.test.TestCoroutineScope
+import kotlinx.coroutines.test.runBlockingTest
+import org.junit.jupiter.api.Assertions.assertEquals
+import org.junit.jupiter.api.BeforeEach
+import org.junit.jupiter.api.Test
+import org.opendc.simulator.compute.model.MemoryUnit
+import org.opendc.simulator.compute.model.ProcessingNode
+import org.opendc.simulator.compute.model.ProcessingUnit
+import org.opendc.simulator.compute.workload.SimFlopsWorkload
+import org.opendc.simulator.utils.DelayControllerClockAdapter
+
+/**
+ * Test suite for the [SimMachine] class.
+ */
+@OptIn(ExperimentalCoroutinesApi::class)
+class SimMachineTest {
+ private lateinit var machineModel: SimMachineModel
+
+ @BeforeEach
+ fun setUp() {
+ val cpuNode = ProcessingNode("Intel", "Xeon", "amd64", 2)
+
+ machineModel = SimMachineModel(
+ cpus = List(cpuNode.coreCount) { ProcessingUnit(cpuNode, it, 1000.0) },
+ memory = List(4) { MemoryUnit("Crucial", "MTA18ASF4G72AZ-3G2B1", 3200.0, 32_000) }
+ )
+ }
+
+ @Test
+ fun testFlopsWorkload() {
+ val testScope = TestCoroutineScope()
+ val clock = DelayControllerClockAdapter(testScope)
+ val machine = SimMachine(testScope, clock, machineModel)
+
+ testScope.runBlockingTest {
+ machine.run(SimFlopsWorkload(2_000, 2, utilization = 1.0))
+
+ // Two cores execute 1000 MFlOps per second (1000 ms)
+ assertEquals(1000, testScope.currentTime)
+ }
+ }
+
+ @Test
+ fun testUsage() {
+ val testScope = TestCoroutineScope()
+ val clock = DelayControllerClockAdapter(testScope)
+ val machine = SimMachine(testScope, clock, machineModel)
+
+ testScope.runBlockingTest {
+ machine.run(SimFlopsWorkload(2_000, 2, utilization = 1.0))
+ assertEquals(1.0, machine.usage.value)
+
+ // Wait for the usage to reset
+ delay(1)
+ assertEquals(0.0, machine.usage.value)
+ }
+ }
+}
diff --git a/simulator/opendc-simulator/opendc-simulator-compute/src/test/kotlin/org/opendc/simulator/compute/workload/SimFlopsWorkloadTest.kt b/simulator/opendc-simulator/opendc-simulator-compute/src/test/kotlin/org/opendc/simulator/compute/workload/SimFlopsWorkloadTest.kt
new file mode 100644
index 00000000..51bed76c
--- /dev/null
+++ b/simulator/opendc-simulator/opendc-simulator-compute/src/test/kotlin/org/opendc/simulator/compute/workload/SimFlopsWorkloadTest.kt
@@ -0,0 +1,73 @@
+/*
+ * Copyright (c) 2020 AtLarge Research
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
+ * of this software and associated documentation files (the "Software"), to deal
+ * in the Software without restriction, including without limitation the rights
+ * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+ * copies of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included in all
+ * copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
+ * SOFTWARE.
+ */
+
+package org.opendc.simulator.compute.workload
+
+import org.junit.jupiter.api.Test
+import org.junit.jupiter.api.assertThrows
+
+/**
+ * Test suite for [SimFlopsWorkload] class.
+ */
+class SimFlopsWorkloadTest {
+ @Test
+ fun testFlopsNonNegative() {
+ assertThrows<IllegalArgumentException>("FLOPs must be non-negative") {
+ SimFlopsWorkload(-1, 1)
+ }
+ }
+
+ @Test
+ fun testCoresNonZero() {
+ assertThrows<IllegalArgumentException>("Cores cannot be zero") {
+ SimFlopsWorkload(1, 0)
+ }
+ }
+
+ @Test
+ fun testCoresPositive() {
+ assertThrows<IllegalArgumentException>("Cores cannot be negative") {
+ SimFlopsWorkload(1, -1)
+ }
+ }
+
+ @Test
+ fun testUtilizationNonZero() {
+ assertThrows<IllegalArgumentException>("Utilization cannot be zero") {
+ SimFlopsWorkload(1, 1, 0.0)
+ }
+ }
+
+ @Test
+ fun testUtilizationPositive() {
+ assertThrows<IllegalArgumentException>("Utilization cannot be negative") {
+ SimFlopsWorkload(1, 1, -1.0)
+ }
+ }
+
+ @Test
+ fun testUtilizationNotLargerThanOne() {
+ assertThrows<IllegalArgumentException>("Utilization cannot be larger than one") {
+ SimFlopsWorkload(1, 1, 2.0)
+ }
+ }
+}
diff --git a/simulator/opendc-simulator/opendc-simulator-core/build.gradle.kts b/simulator/opendc-simulator/opendc-simulator-core/build.gradle.kts
new file mode 100644
index 00000000..a740dcf3
--- /dev/null
+++ b/simulator/opendc-simulator/opendc-simulator-core/build.gradle.kts
@@ -0,0 +1,32 @@
+/*
+ * Copyright (c) 2020 AtLarge Research
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
+ * of this software and associated documentation files (the "Software"), to deal
+ * in the Software without restriction, including without limitation the rights
+ * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+ * copies of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included in all
+ * copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
+ * SOFTWARE.
+ */
+
+description = "Simulation-specific code for use in OpenDC"
+
+/* Build configuration */
+plugins {
+ `kotlin-library-convention`
+}
+
+dependencies {
+ api("org.jetbrains.kotlinx:kotlinx-coroutines-test:${Library.KOTLINX_COROUTINES}")
+}
diff --git a/simulator/opendc-simulator/src/main/kotlin/org/opendc/simulator/utils/DelayControllerClockAdapter.kt b/simulator/opendc-simulator/opendc-simulator-core/src/main/kotlin/org/opendc/simulator/utils/DelayControllerClockAdapter.kt
index 84c18e87..84c18e87 100644
--- a/simulator/opendc-simulator/src/main/kotlin/org/opendc/simulator/utils/DelayControllerClockAdapter.kt
+++ b/simulator/opendc-simulator/opendc-simulator-core/src/main/kotlin/org/opendc/simulator/utils/DelayControllerClockAdapter.kt
diff --git a/simulator/opendc-workflows/build.gradle.kts b/simulator/opendc-workflows/build.gradle.kts
index 97c588da..efffe7a1 100644
--- a/simulator/opendc-workflows/build.gradle.kts
+++ b/simulator/opendc-workflows/build.gradle.kts
@@ -32,7 +32,7 @@ dependencies {
api(project(":opendc-compute"))
implementation(project(":opendc-utils"))
- testImplementation(project(":opendc-simulator"))
+ testImplementation(project(":opendc-simulator:opendc-simulator-core"))
testImplementation(project(":opendc-format"))
testImplementation("org.junit.jupiter:junit-jupiter-api:${Library.JUNIT_JUPITER}")
testRuntimeOnly("org.junit.jupiter:junit-jupiter-engine:${Library.JUNIT_JUPITER}")
diff --git a/simulator/settings.gradle.kts b/simulator/settings.gradle.kts
index e5e218b8..3de05fe1 100644
--- a/simulator/settings.gradle.kts
+++ b/simulator/settings.gradle.kts
@@ -28,5 +28,6 @@ include(":opendc-format")
include(":opendc-experiments:opendc-experiments-sc18")
include(":opendc-experiments:opendc-experiments-sc20")
include(":opendc-runner-web")
-include(":opendc-simulator")
+include(":opendc-simulator:opendc-simulator-core")
+include(":opendc-simulator:opendc-simulator-compute")
include(":opendc-utils")