summaryrefslogtreecommitdiff
path: root/opendc-experiments/opendc-experiments-base/src/main
diff options
context:
space:
mode:
Diffstat (limited to 'opendc-experiments/opendc-experiments-base/src/main')
-rw-r--r--opendc-experiments/opendc-experiments-base/src/main/kotlin/org/opendc/experiments/base/runner/ScenarioHelpers.kt20
-rw-r--r--opendc-experiments/opendc-experiments-base/src/main/kotlin/org/opendc/experiments/base/runner/ScenarioRunner.kt45
-rw-r--r--opendc-experiments/opendc-experiments-base/src/main/kotlin/org/opendc/experiments/base/scenario/Scenario.kt (renamed from opendc-experiments/opendc-experiments-base/src/main/kotlin/org/opendc/experiments/base/models/scenario/Scenario.kt)10
-rw-r--r--opendc-experiments/opendc-experiments-base/src/main/kotlin/org/opendc/experiments/base/scenario/ScenarioFactories.kt (renamed from opendc-experiments/opendc-experiments-base/src/main/kotlin/org/opendc/experiments/base/models/scenario/ScenarioFactories.kt)82
-rw-r--r--opendc-experiments/opendc-experiments-base/src/main/kotlin/org/opendc/experiments/base/scenario/ScenarioReader.kt (renamed from opendc-experiments/opendc-experiments-base/src/main/kotlin/org/opendc/experiments/base/models/scenario/ScenarioReader.kt)4
-rw-r--r--opendc-experiments/opendc-experiments-base/src/main/kotlin/org/opendc/experiments/base/scenario/specs/AllocationPolicySpec.kt (renamed from opendc-experiments/opendc-experiments-base/src/main/kotlin/org/opendc/experiments/base/models/scenario/specs/AllocationPolicySpec.kt)0
-rw-r--r--opendc-experiments/opendc-experiments-base/src/main/kotlin/org/opendc/experiments/base/scenario/specs/ExportModelSpec.kt (renamed from opendc-experiments/opendc-experiments-base/src/main/kotlin/org/opendc/experiments/base/models/scenario/specs/ExportModelSpec.kt)0
-rw-r--r--opendc-experiments/opendc-experiments-base/src/main/kotlin/org/opendc/experiments/base/scenario/specs/FailureModelSpec.kt (renamed from opendc-experiments/opendc-experiments-base/src/main/kotlin/org/opendc/experiments/base/models/scenario/specs/FailureModelSpec.kt)0
-rw-r--r--opendc-experiments/opendc-experiments-base/src/main/kotlin/org/opendc/experiments/base/scenario/specs/PowerModelSpec.kt (renamed from opendc-experiments/opendc-experiments-base/src/main/kotlin/org/opendc/experiments/base/models/scenario/specs/PowerModelSpec.kt)0
-rw-r--r--opendc-experiments/opendc-experiments-base/src/main/kotlin/org/opendc/experiments/base/scenario/specs/ScenarioSpec.kt (renamed from opendc-experiments/opendc-experiments-base/src/main/kotlin/org/opendc/experiments/base/models/scenario/specs/ScenarioSpec.kt)6
-rw-r--r--opendc-experiments/opendc-experiments-base/src/main/kotlin/org/opendc/experiments/base/scenario/specs/ScenarioTopologySpec.kt (renamed from opendc-experiments/opendc-experiments-base/src/main/kotlin/org/opendc/experiments/base/models/scenario/specs/TopologySpec.kt)2
-rw-r--r--opendc-experiments/opendc-experiments-base/src/main/kotlin/org/opendc/experiments/base/scenario/specs/WorkloadSpec.kt (renamed from opendc-experiments/opendc-experiments-base/src/main/kotlin/org/opendc/experiments/base/models/scenario/specs/WorkloadSpec.kt)0
12 files changed, 50 insertions, 119 deletions
diff --git a/opendc-experiments/opendc-experiments-base/src/main/kotlin/org/opendc/experiments/base/runner/ScenarioHelpers.kt b/opendc-experiments/opendc-experiments-base/src/main/kotlin/org/opendc/experiments/base/runner/ScenarioHelpers.kt
index 97914556..e1305b3f 100644
--- a/opendc-experiments/opendc-experiments-base/src/main/kotlin/org/opendc/experiments/base/runner/ScenarioHelpers.kt
+++ b/opendc-experiments/opendc-experiments-base/src/main/kotlin/org/opendc/experiments/base/runner/ScenarioHelpers.kt
@@ -24,6 +24,7 @@
package org.opendc.experiments.base.runner
+import FailureModelSpec
import kotlinx.coroutines.coroutineScope
import kotlinx.coroutines.delay
import kotlinx.coroutines.launch
@@ -33,11 +34,8 @@ import org.opendc.compute.api.Server
import org.opendc.compute.api.ServerState
import org.opendc.compute.api.ServerWatcher
import org.opendc.compute.service.ComputeService
-import org.opendc.compute.simulator.failure.FailureModel
import org.opendc.compute.workload.VirtualMachine
import java.time.InstantSource
-import java.util.Random
-import kotlin.coroutines.coroutineContext
import kotlin.math.max
/**
@@ -76,16 +74,16 @@ public class RunningServerWatcher : ServerWatcher {
* @param trace The trace to simulate.
* @param seed The seed to use for randomness.
* @param submitImmediately A flag to indicate that the servers are scheduled immediately (so not at their start time).
- * @param failureModel A failure model to use for injecting failures.
+ * @param failureModelSpec A failure model to use for injecting failures.
*/
public suspend fun ComputeService.replay(
clock: InstantSource,
trace: List<VirtualMachine>,
- seed: Long,
+ failureModelSpec: FailureModelSpec? = null,
+ seed: Long = 0,
submitImmediately: Boolean = false,
- failureModel: FailureModel? = null,
) {
- val injector = failureModel?.createInjector(coroutineContext, clock, this, Random(seed))
+ // TODO: add failureModel functionality
val client = newClient()
// Create new image for the virtual machine
@@ -93,8 +91,7 @@ public suspend fun ComputeService.replay(
try {
coroutineScope {
- // Start the fault injector
- injector?.start()
+ // TODO: start failure model when implemented
var simulationOffset = Long.MIN_VALUE
@@ -107,9 +104,6 @@ public suspend fun ComputeService.replay(
simulationOffset = start - now
}
- // Make sure the trace entries are ordered by submission time
- // assert(start - simulationOffset >= 0) { "Invalid trace order" }
-
// Delay the server based on the startTime given by the trace.
if (!submitImmediately) {
delay(max(0, (start - now - simulationOffset)))
@@ -146,7 +140,7 @@ public suspend fun ComputeService.replay(
}
yield()
} finally {
- injector?.close()
+ // TODO: close failure model when implemented
client.close()
}
}
diff --git a/opendc-experiments/opendc-experiments-base/src/main/kotlin/org/opendc/experiments/base/runner/ScenarioRunner.kt b/opendc-experiments/opendc-experiments-base/src/main/kotlin/org/opendc/experiments/base/runner/ScenarioRunner.kt
index 734cdacc..bf5188a2 100644
--- a/opendc-experiments/opendc-experiments-base/src/main/kotlin/org/opendc/experiments/base/runner/ScenarioRunner.kt
+++ b/opendc-experiments/opendc-experiments-base/src/main/kotlin/org/opendc/experiments/base/runner/ScenarioRunner.kt
@@ -35,8 +35,9 @@ import org.opendc.compute.simulator.provisioner.registerComputeMonitor
import org.opendc.compute.simulator.provisioner.setupComputeService
import org.opendc.compute.simulator.provisioner.setupHosts
import org.opendc.compute.telemetry.export.parquet.ParquetComputeMonitor
+import org.opendc.compute.topology.clusterTopology
import org.opendc.compute.workload.ComputeWorkloadLoader
-import org.opendc.experiments.base.models.scenario.Scenario
+import org.opendc.experiments.base.scenario.Scenario
import org.opendc.simulator.kotlin.runSimulation
import java.io.File
import java.time.Duration
@@ -47,10 +48,10 @@ import java.util.stream.LongStream
/**
* Run scenario when no pool is available for parallel execution
*
- * @param scenario The scenario to run
+ * @param scenarios The scenarios to run
* @param parallelism The number of scenarios that can be run in parallel
*/
-public fun runScenario(
+public fun runScenarios(
scenarios: List<Scenario>,
parallelism: Int,
) {
@@ -110,12 +111,14 @@ public fun runScenario(
runSimulation {
val serviceDomain = "compute.opendc.org"
Provisioner(dispatcher, seed).use { provisioner ->
+
+ val topology = clusterTopology(scenario.topology.pathToFile, Random(seed))
provisioner.runSteps(
setupComputeService(
serviceDomain,
{ createComputeScheduler(ComputeSchedulerEnum.Mem, Random(it.seeder.nextLong())) },
),
- setupHosts(serviceDomain, scenario.topology, optimize = true),
+ setupHosts(serviceDomain, topology, optimize = true),
)
val workloadLoader = ComputeWorkloadLoader(File(scenario.workload.pathToFile))
@@ -126,43 +129,11 @@ public fun runScenario(
saveInOutputFolder(provisioner, serviceDomain, scenario, seed, startTime, carbonTrace)
val service = provisioner.registry.resolve(serviceDomain, ComputeService::class.java)!!
- service.replay(timeSource, vms, seed, failureModel = scenario.failureModel)
+ service.replay(timeSource, vms, failureModelSpec = scenario.failureModel, seed = seed)
}
}
/**
- * When the simulation is run, saves the simulation results into a seed folder. This is useful for debugging purposes.
- * @param provisioner The provisioner used to setup and run the simulation.
- * @param serviceDomain The domain of the compute service.
- * @param scenario The scenario being run in the simulation.
- * @param seed The seed used for randomness in the simulation.
- * @param partition The partition name for the output data.
- * @param startTime The start time of the simulation.
-
- */
-public fun saveInSeedFolder(
- provisioner: Provisioner,
- serviceDomain: String,
- scenario: Scenario,
- seed: Long,
- partition: String,
- startTime: Duration,
-) {
- provisioner.runStep(
- registerComputeMonitor(
- serviceDomain,
- ParquetComputeMonitor(
- File(scenario.outputFolder),
- partition,
- bufferSize = 4096,
- ),
- Duration.ofSeconds(scenario.exportModel.exportInterval),
- startTime,
- ),
- )
-}
-
-/**
* Saves the simulation results into a specific output folder received from the input.
*
* @param provisioner The provisioner used to setup and run the simulation.
diff --git a/opendc-experiments/opendc-experiments-base/src/main/kotlin/org/opendc/experiments/base/models/scenario/Scenario.kt b/opendc-experiments/opendc-experiments-base/src/main/kotlin/org/opendc/experiments/base/scenario/Scenario.kt
index bd4b5cc9..4f3fcd4f 100644
--- a/opendc-experiments/opendc-experiments-base/src/main/kotlin/org/opendc/experiments/base/models/scenario/Scenario.kt
+++ b/opendc-experiments/opendc-experiments-base/src/main/kotlin/org/opendc/experiments/base/scenario/Scenario.kt
@@ -20,13 +20,13 @@
* SOFTWARE.
*/
-package org.opendc.experiments.base.models.scenario
+package org.opendc.experiments.base.scenario
import AllocationPolicySpec
import ExportModelSpec
+import FailureModelSpec
+import ScenarioTopologySpec
import WorkloadSpec
-import org.opendc.compute.simulator.failure.FailureModel
-import org.opendc.compute.topology.specs.HostSpec
/**
* A data class representing a scenario for a set of experiments.
@@ -42,10 +42,10 @@ import org.opendc.compute.topology.specs.HostSpec
* @property initialSeed The Int representing the initial seed of the scenario. It defaults to 0.
*/
public data class Scenario(
- val topology: List<HostSpec>,
+ val topology: ScenarioTopologySpec,
val workload: WorkloadSpec,
val allocationPolicy: AllocationPolicySpec,
- val failureModel: FailureModel?,
+ val failureModel: FailureModelSpec?,
val carbonTracePath: String? = null,
val exportModel: ExportModelSpec = ExportModelSpec(),
val outputFolder: String = "output",
diff --git a/opendc-experiments/opendc-experiments-base/src/main/kotlin/org/opendc/experiments/base/models/scenario/ScenarioFactories.kt b/opendc-experiments/opendc-experiments-base/src/main/kotlin/org/opendc/experiments/base/scenario/ScenarioFactories.kt
index 05fa762e..010c8845 100644
--- a/opendc-experiments/opendc-experiments-base/src/main/kotlin/org/opendc/experiments/base/models/scenario/ScenarioFactories.kt
+++ b/opendc-experiments/opendc-experiments-base/src/main/kotlin/org/opendc/experiments/base/scenario/ScenarioFactories.kt
@@ -20,16 +20,12 @@
* SOFTWARE.
*/
-package org.opendc.experiments.base.models.scenario
+package org.opendc.experiments.base.scenario
import AllocationPolicySpec
-import TopologySpec
+import ScenarioTopologySpec
import WorkloadSpec
-import org.opendc.compute.simulator.failure.getFailureModel
-import org.opendc.compute.topology.TopologyReader
-import org.opendc.compute.topology.clusterTopology
-import org.opendc.compute.topology.specs.TopologyJSONSpec
-import org.opendc.experiments.base.models.scenario.specs.ScenarioSpec
+import org.opendc.experiments.base.scenario.specs.ScenarioSpec
import java.io.File
private val scenarioReader = ScenarioReader()
@@ -40,8 +36,8 @@ private val scenarioReader = ScenarioReader()
* @param filePath The path to the file containing the scenario specifications.
* @return A list of Scenarios.
*/
-public fun getScenario(filePath: String): List<Scenario> {
- return getScenario(File(filePath))
+public fun getScenarios(filePath: String): List<Scenario> {
+ return getScenarios(File(filePath))
}
/**
@@ -50,18 +46,8 @@ public fun getScenario(filePath: String): List<Scenario> {
* @param file The file containing the scenario specifications.
* @return A list of Scenarios.
*/
-public fun getScenario(file: File): List<Scenario> {
- return getScenario(scenarioReader.read(file))
-}
-
-/**
- * Returns a list of Scenarios from a given ScenarioSpec.
- *
- * @param scenarioSpec The ScenarioSpec containing the scenario specifications.
- * @return A list of Scenarios.
- */
-public fun getScenario(scenarioSpec: ScenarioSpec): List<Scenario> {
- return getScenarioCombinations(scenarioSpec)
+public fun getScenarios(file: File): List<Scenario> {
+ return getScenarios(scenarioReader.read(file))
}
/**
@@ -71,30 +57,25 @@ public fun getScenario(scenarioSpec: ScenarioSpec): List<Scenario> {
* @param scenarioSpec The ScenarioSpec containing the scenario specifications.
* @return A list of Scenarios.
*/
-public fun getScenarioCombinations(scenarioSpec: ScenarioSpec): List<Scenario> {
- val topologiesSpec = scenarioSpec.topologies
- val workloads = scenarioSpec.workloads
- val allocationPolicies = scenarioSpec.allocationPolicies
- val failureModels = scenarioSpec.failureModels
- val exportModels = scenarioSpec.exportModels
+public fun getScenarios(scenarioSpec: ScenarioSpec): List<Scenario> {
val scenarios = mutableListOf<Scenario>()
- for (topology in topologiesSpec) {
- for (workload in workloads) {
- for (allocationPolicy in allocationPolicies) {
- for (failureModel in failureModels) {
+ for (scenarioTopologySpec in scenarioSpec.topologies) {
+ for (workloadSpec in scenarioSpec.workloads) {
+ for (allocationPolicySpec in scenarioSpec.allocationPolicies) {
+ for (failureModelSpec in scenarioSpec.failureModels) {
for (carbonTracePath in scenarioSpec.carbonTracePaths) {
- for (exportModel in exportModels) {
+ for (exportModelSpec in scenarioSpec.exportModels) {
val scenario =
Scenario(
- topology = clusterTopology(File(topology.pathToFile)),
- workload = workload,
- allocationPolicy = allocationPolicy,
- failureModel = getFailureModel(failureModel.failureInterval),
+ topology = scenarioTopologySpec,
+ workload = workloadSpec,
+ allocationPolicy = allocationPolicySpec,
+ failureModel = failureModelSpec,
carbonTracePath = carbonTracePath,
- exportModel = exportModel,
+ exportModel = exportModelSpec,
outputFolder = scenarioSpec.outputFolder,
- name = getOutputFolderName(scenarioSpec, topology, workload, allocationPolicy),
+ name = getOutputFolderName(scenarioSpec, scenarioTopologySpec, workloadSpec, allocationPolicySpec),
runs = scenarioSpec.runs,
initialSeed = scenarioSpec.initialSeed,
)
@@ -110,37 +91,22 @@ public fun getScenarioCombinations(scenarioSpec: ScenarioSpec): List<Scenario> {
}
/**
- * Returns a list of TopologyJSONSpec from a given list of TopologySpec.
- *
- * @param topologies The list of TopologySpec.
- * @return A list of TopologyJSONSpec.
- */
-public fun getTopologies(topologies: List<TopologySpec>): List<TopologyJSONSpec> {
- val readTopologies = mutableListOf<TopologyJSONSpec>()
- for (topology in topologies) {
- readTopologies.add(TopologyReader().read(File(topology.pathToFile)))
- }
-
- return readTopologies
-}
-
-/**
* Returns a string representing the output folder name for a given ScenarioSpec, CpuPowerModel, AllocationPolicySpec, and topology path.
*
* @param scenarioSpec The ScenarioSpec.
- * @param powerModel The CpuPowerModel.
- * @param allocationPolicy The AllocationPolicySpec.
- * @param topologyPath The path to the topology file.
+ * @param topology The specification of the topology used
+ * @param workload The specification of the workload
+ * @param allocationPolicy The allocation policy used
* @return A string representing the output folder name.
*/
public fun getOutputFolderName(
scenarioSpec: ScenarioSpec,
- topology: TopologySpec,
+ topology: ScenarioTopologySpec,
workload: WorkloadSpec,
allocationPolicy: AllocationPolicySpec,
): String {
return "scenario=${scenarioSpec.name}" +
"-topology=${topology.name}" +
"-workload=${workload.name}" +
- "-scheduler=${allocationPolicy.name}"
+ "-allocation=${allocationPolicy.name}"
}
diff --git a/opendc-experiments/opendc-experiments-base/src/main/kotlin/org/opendc/experiments/base/models/scenario/ScenarioReader.kt b/opendc-experiments/opendc-experiments-base/src/main/kotlin/org/opendc/experiments/base/scenario/ScenarioReader.kt
index ffbb3aa3..19ce5a14 100644
--- a/opendc-experiments/opendc-experiments-base/src/main/kotlin/org/opendc/experiments/base/models/scenario/ScenarioReader.kt
+++ b/opendc-experiments/opendc-experiments-base/src/main/kotlin/org/opendc/experiments/base/scenario/ScenarioReader.kt
@@ -20,12 +20,12 @@
* SOFTWARE.
*/
-package org.opendc.experiments.base.models.scenario
+package org.opendc.experiments.base.scenario
import kotlinx.serialization.ExperimentalSerializationApi
import kotlinx.serialization.json.Json
import kotlinx.serialization.json.decodeFromStream
-import org.opendc.experiments.base.models.scenario.specs.ScenarioSpec
+import org.opendc.experiments.base.scenario.specs.ScenarioSpec
import java.io.File
import java.io.InputStream
diff --git a/opendc-experiments/opendc-experiments-base/src/main/kotlin/org/opendc/experiments/base/models/scenario/specs/AllocationPolicySpec.kt b/opendc-experiments/opendc-experiments-base/src/main/kotlin/org/opendc/experiments/base/scenario/specs/AllocationPolicySpec.kt
index f7ae7e9f..f7ae7e9f 100644
--- a/opendc-experiments/opendc-experiments-base/src/main/kotlin/org/opendc/experiments/base/models/scenario/specs/AllocationPolicySpec.kt
+++ b/opendc-experiments/opendc-experiments-base/src/main/kotlin/org/opendc/experiments/base/scenario/specs/AllocationPolicySpec.kt
diff --git a/opendc-experiments/opendc-experiments-base/src/main/kotlin/org/opendc/experiments/base/models/scenario/specs/ExportModelSpec.kt b/opendc-experiments/opendc-experiments-base/src/main/kotlin/org/opendc/experiments/base/scenario/specs/ExportModelSpec.kt
index 9a23ad00..9a23ad00 100644
--- a/opendc-experiments/opendc-experiments-base/src/main/kotlin/org/opendc/experiments/base/models/scenario/specs/ExportModelSpec.kt
+++ b/opendc-experiments/opendc-experiments-base/src/main/kotlin/org/opendc/experiments/base/scenario/specs/ExportModelSpec.kt
diff --git a/opendc-experiments/opendc-experiments-base/src/main/kotlin/org/opendc/experiments/base/models/scenario/specs/FailureModelSpec.kt b/opendc-experiments/opendc-experiments-base/src/main/kotlin/org/opendc/experiments/base/scenario/specs/FailureModelSpec.kt
index 99620366..99620366 100644
--- a/opendc-experiments/opendc-experiments-base/src/main/kotlin/org/opendc/experiments/base/models/scenario/specs/FailureModelSpec.kt
+++ b/opendc-experiments/opendc-experiments-base/src/main/kotlin/org/opendc/experiments/base/scenario/specs/FailureModelSpec.kt
diff --git a/opendc-experiments/opendc-experiments-base/src/main/kotlin/org/opendc/experiments/base/models/scenario/specs/PowerModelSpec.kt b/opendc-experiments/opendc-experiments-base/src/main/kotlin/org/opendc/experiments/base/scenario/specs/PowerModelSpec.kt
index fc568925..fc568925 100644
--- a/opendc-experiments/opendc-experiments-base/src/main/kotlin/org/opendc/experiments/base/models/scenario/specs/PowerModelSpec.kt
+++ b/opendc-experiments/opendc-experiments-base/src/main/kotlin/org/opendc/experiments/base/scenario/specs/PowerModelSpec.kt
diff --git a/opendc-experiments/opendc-experiments-base/src/main/kotlin/org/opendc/experiments/base/models/scenario/specs/ScenarioSpec.kt b/opendc-experiments/opendc-experiments-base/src/main/kotlin/org/opendc/experiments/base/scenario/specs/ScenarioSpec.kt
index 15012461..cfbb913c 100644
--- a/opendc-experiments/opendc-experiments-base/src/main/kotlin/org/opendc/experiments/base/models/scenario/specs/ScenarioSpec.kt
+++ b/opendc-experiments/opendc-experiments-base/src/main/kotlin/org/opendc/experiments/base/scenario/specs/ScenarioSpec.kt
@@ -20,12 +20,12 @@
* SOFTWARE.
*/
-package org.opendc.experiments.base.models.scenario.specs
+package org.opendc.experiments.base.scenario.specs
import AllocationPolicySpec
import ExportModelSpec
import FailureModelSpec
-import TopologySpec
+import ScenarioTopologySpec
import WorkloadSpec
import kotlinx.serialization.Serializable
@@ -43,7 +43,7 @@ import kotlinx.serialization.Serializable
*/
@Serializable
public data class ScenarioSpec(
- val topologies: List<TopologySpec>,
+ val topologies: List<ScenarioTopologySpec>,
val workloads: List<WorkloadSpec>,
val allocationPolicies: List<AllocationPolicySpec>,
val failureModels: List<FailureModelSpec> = listOf(FailureModelSpec()),
diff --git a/opendc-experiments/opendc-experiments-base/src/main/kotlin/org/opendc/experiments/base/models/scenario/specs/TopologySpec.kt b/opendc-experiments/opendc-experiments-base/src/main/kotlin/org/opendc/experiments/base/scenario/specs/ScenarioTopologySpec.kt
index 392b9763..47d3447f 100644
--- a/opendc-experiments/opendc-experiments-base/src/main/kotlin/org/opendc/experiments/base/models/scenario/specs/TopologySpec.kt
+++ b/opendc-experiments/opendc-experiments-base/src/main/kotlin/org/opendc/experiments/base/scenario/specs/ScenarioTopologySpec.kt
@@ -29,7 +29,7 @@ import java.io.File
* @property pathToFile
*/
@Serializable
-public data class TopologySpec(
+public data class ScenarioTopologySpec(
val pathToFile: String,
) {
public val name: String = File(pathToFile).nameWithoutExtension
diff --git a/opendc-experiments/opendc-experiments-base/src/main/kotlin/org/opendc/experiments/base/models/scenario/specs/WorkloadSpec.kt b/opendc-experiments/opendc-experiments-base/src/main/kotlin/org/opendc/experiments/base/scenario/specs/WorkloadSpec.kt
index 819f633d..819f633d 100644
--- a/opendc-experiments/opendc-experiments-base/src/main/kotlin/org/opendc/experiments/base/models/scenario/specs/WorkloadSpec.kt
+++ b/opendc-experiments/opendc-experiments-base/src/main/kotlin/org/opendc/experiments/base/scenario/specs/WorkloadSpec.kt