summaryrefslogtreecommitdiff
path: root/simulator
diff options
context:
space:
mode:
Diffstat (limited to 'simulator')
-rw-r--r--simulator/buildSrc/build.gradle.kts4
-rw-r--r--simulator/buildSrc/src/main/kotlin/kotlin-library-convention.gradle.kts1
-rw-r--r--simulator/buildSrc/src/main/kotlin/library.kt2
-rw-r--r--simulator/gradle/wrapper/gradle-wrapper.properties2
-rw-r--r--simulator/opendc-compute/opendc-compute-core/build.gradle.kts1
-rw-r--r--simulator/opendc-compute/opendc-compute-core/src/main/kotlin/org/opendc/compute/core/virt/driver/VirtDriver.kt8
-rw-r--r--simulator/opendc-compute/opendc-compute-core/src/main/kotlin/org/opendc/compute/core/virt/service/VirtProvisioningService.kt5
-rw-r--r--simulator/opendc-compute/opendc-compute-core/src/main/kotlin/org/opendc/compute/core/virt/service/events/HypervisorAvailableEvent.kt (renamed from simulator/opendc-workflows/src/main/kotlin/org/opendc/workflows/service/stage/resource/ResourceSelectionPolicy.kt)11
-rw-r--r--simulator/opendc-compute/opendc-compute-core/src/main/kotlin/org/opendc/compute/core/virt/service/events/HypervisorUnavailableEvent.kt31
-rw-r--r--simulator/opendc-compute/opendc-compute-core/src/main/kotlin/org/opendc/compute/core/virt/service/events/VmScheduledEvent.kt30
-rw-r--r--simulator/opendc-compute/opendc-compute-core/src/main/kotlin/org/opendc/compute/core/virt/service/events/VmStoppedEvent.kt30
-rw-r--r--simulator/opendc-compute/opendc-compute-core/src/main/kotlin/org/opendc/compute/core/virt/service/events/VmSubmissionEvent.kt32
-rw-r--r--simulator/opendc-compute/opendc-compute-core/src/main/kotlin/org/opendc/compute/core/virt/service/events/VmSubmissionInvalidEvent.kt30
-rw-r--r--simulator/opendc-compute/opendc-compute-simulator/build.gradle.kts4
-rw-r--r--simulator/opendc-compute/opendc-compute-simulator/src/main/kotlin/org/opendc/compute/simulator/SimBareMetalDriver.kt23
-rw-r--r--simulator/opendc-compute/opendc-compute-simulator/src/main/kotlin/org/opendc/compute/simulator/SimVirtDriver.kt109
-rw-r--r--simulator/opendc-compute/opendc-compute-simulator/src/main/kotlin/org/opendc/compute/simulator/SimVirtProvisioningService.kt69
-rw-r--r--simulator/opendc-compute/opendc-compute-simulator/src/main/kotlin/org/opendc/compute/simulator/allocation/ComparableAllocationPolicyLogic.kt2
-rw-r--r--simulator/opendc-compute/opendc-compute-simulator/src/test/kotlin/org/opendc/compute/simulator/SimBareMetalDriverTest.kt4
-rw-r--r--simulator/opendc-compute/opendc-compute-simulator/src/test/kotlin/org/opendc/compute/simulator/SimProvisioningServiceTest.kt2
-rw-r--r--simulator/opendc-compute/opendc-compute-simulator/src/test/kotlin/org/opendc/compute/simulator/SimVirtDriverTest.kt52
-rw-r--r--simulator/opendc-experiments/opendc-experiments-sc18/build.gradle.kts4
-rw-r--r--simulator/opendc-experiments/opendc-experiments-sc18/src/main/kotlin/org/opendc/experiments/sc18/TestExperiment.kt115
-rw-r--r--simulator/opendc-experiments/opendc-experiments-sc18/src/main/kotlin/org/opendc/experiments/sc18/UnderspecificationExperiment.kt135
-rw-r--r--simulator/opendc-experiments/opendc-experiments-sc18/src/main/kotlin/org/opendc/experiments/sc18/WorkflowMetrics.kt86
-rw-r--r--simulator/opendc-experiments/opendc-experiments-sc18/src/main/resources/env/setup-test.json36
-rw-r--r--simulator/opendc-experiments/opendc-experiments-sc20/build.gradle.kts11
-rw-r--r--simulator/opendc-experiments/opendc-experiments-sc20/src/main/kotlin/org/opendc/experiments/sc20/Main.kt159
-rw-r--r--simulator/opendc-experiments/opendc-experiments-sc20/src/main/kotlin/org/opendc/experiments/sc20/experiment/CompositeWorkloadPortfolio.kt79
-rw-r--r--simulator/opendc-experiments/opendc-experiments-sc20/src/main/kotlin/org/opendc/experiments/sc20/experiment/Experiment.kt76
-rw-r--r--simulator/opendc-experiments/opendc-experiments-sc20/src/main/kotlin/org/opendc/experiments/sc20/experiment/ExperimentHelpers.kt7
-rw-r--r--simulator/opendc-experiments/opendc-experiments-sc20/src/main/kotlin/org/opendc/experiments/sc20/experiment/HorVerPortfolio.kt60
-rw-r--r--simulator/opendc-experiments/opendc-experiments-sc20/src/main/kotlin/org/opendc/experiments/sc20/experiment/MoreHpcPortfolio.kt56
-rw-r--r--simulator/opendc-experiments/opendc-experiments-sc20/src/main/kotlin/org/opendc/experiments/sc20/experiment/MoreVelocityPortfolio.kt56
-rw-r--r--simulator/opendc-experiments/opendc-experiments-sc20/src/main/kotlin/org/opendc/experiments/sc20/experiment/OperationalPhenomenaPortfolio.kt61
-rw-r--r--simulator/opendc-experiments/opendc-experiments-sc20/src/main/kotlin/org/opendc/experiments/sc20/experiment/Portfolio.kt199
-rw-r--r--simulator/opendc-experiments/opendc-experiments-sc20/src/main/kotlin/org/opendc/experiments/sc20/experiment/Portfolios.kt221
-rw-r--r--simulator/opendc-experiments/opendc-experiments-sc20/src/main/kotlin/org/opendc/experiments/sc20/experiment/ReplayPortfolio.kt (renamed from simulator/opendc-experiments/opendc-experiments-sc20/src/main/kotlin/org/opendc/experiments/sc20/experiment/Scenario.kt)36
-rw-r--r--simulator/opendc-experiments/opendc-experiments-sc20/src/main/kotlin/org/opendc/experiments/sc20/experiment/Run.kt153
-rw-r--r--simulator/opendc-experiments/opendc-experiments-sc20/src/main/kotlin/org/opendc/experiments/sc20/experiment/TestPortfolio.kt47
-rw-r--r--simulator/opendc-experiments/opendc-experiments-sc20/src/main/kotlin/org/opendc/experiments/sc20/runner/ContainerExperimentDescriptor.kt66
-rw-r--r--simulator/opendc-experiments/opendc-experiments-sc20/src/main/kotlin/org/opendc/experiments/sc20/runner/ExperimentDescriptor.kt79
-rw-r--r--simulator/opendc-experiments/opendc-experiments-sc20/src/main/kotlin/org/opendc/experiments/sc20/runner/execution/ThreadPoolExperimentScheduler.kt83
-rw-r--r--simulator/opendc-experiments/opendc-experiments-sc20/src/main/kotlin/org/opendc/experiments/sc20/runner/internal/DefaultExperimentRunner.kt60
-rw-r--r--simulator/opendc-experiments/opendc-experiments-sc20/src/main/kotlin/org/opendc/experiments/sc20/telemetry/RunEvent.kt5
-rw-r--r--simulator/opendc-experiments/opendc-experiments-sc20/src/main/kotlin/org/opendc/experiments/sc20/telemetry/parquet/ParquetRunEventWriter.kt28
-rw-r--r--simulator/opendc-experiments/opendc-experiments-sc20/src/main/kotlin/org/opendc/experiments/sc20/trace/Sc20RawParquetTraceReader.kt2
-rw-r--r--simulator/opendc-experiments/opendc-experiments-sc20/src/main/kotlin/org/opendc/experiments/sc20/trace/Sc20StreamingParquetTraceReader.kt8
-rw-r--r--simulator/opendc-experiments/opendc-experiments-sc20/src/test/kotlin/org/opendc/experiments/sc20/Sc20IntegrationTest.kt27
-rw-r--r--simulator/opendc-format/src/main/kotlin/org/opendc/format/trace/bitbrains/BitbrainsTraceReader.kt16
-rw-r--r--simulator/opendc-format/src/main/kotlin/org/opendc/format/trace/gwf/GwfTraceReader.kt12
-rw-r--r--simulator/opendc-format/src/main/kotlin/org/opendc/format/trace/sc20/Sc20TraceReader.kt8
-rw-r--r--simulator/opendc-format/src/main/kotlin/org/opendc/format/trace/swf/SwfTraceReader.kt6
-rw-r--r--simulator/opendc-format/src/main/kotlin/org/opendc/format/trace/wtf/WtfTraceReader.kt8
-rw-r--r--simulator/opendc-format/src/test/kotlin/org/opendc/format/trace/swf/SwfTraceReaderTest.kt1
-rw-r--r--simulator/opendc-harness/build.gradle.kts47
-rw-r--r--simulator/opendc-harness/src/main/kotlin/org/opendc/harness/api/ExperimentDefinition.kt39
-rw-r--r--simulator/opendc-harness/src/main/kotlin/org/opendc/harness/api/Parameter.kt (renamed from simulator/opendc-experiments/opendc-experiments-sc20/src/main/kotlin/org/opendc/experiments/sc20/runner/execution/ExperimentExecutionResult.kt)20
-rw-r--r--simulator/opendc-harness/src/main/kotlin/org/opendc/harness/api/Scenario.kt46
-rw-r--r--simulator/opendc-harness/src/main/kotlin/org/opendc/harness/api/Trial.kt28
-rw-r--r--simulator/opendc-harness/src/main/kotlin/org/opendc/harness/dsl/Experiment.kt99
-rw-r--r--simulator/opendc-harness/src/main/kotlin/org/opendc/harness/dsl/ParameterProvider.kt39
-rw-r--r--simulator/opendc-harness/src/main/kotlin/org/opendc/harness/dsl/Parameters.kt44
-rw-r--r--simulator/opendc-harness/src/main/kotlin/org/opendc/harness/engine/ExperimentEngine.kt94
-rw-r--r--simulator/opendc-harness/src/main/kotlin/org/opendc/harness/engine/ExperimentEngineLauncher.kt121
-rw-r--r--simulator/opendc-harness/src/main/kotlin/org/opendc/harness/engine/ExperimentExecutionListener.kt77
-rw-r--r--simulator/opendc-harness/src/main/kotlin/org/opendc/harness/engine/discovery/Discovery.kt39
-rw-r--r--simulator/opendc-harness/src/main/kotlin/org/opendc/harness/engine/discovery/DiscoveryFilter.kt51
-rw-r--r--simulator/opendc-harness/src/main/kotlin/org/opendc/harness/engine/discovery/DiscoveryProvider.kt65
-rw-r--r--simulator/opendc-harness/src/main/kotlin/org/opendc/harness/engine/discovery/DiscoveryRequest.kt34
-rw-r--r--simulator/opendc-harness/src/main/kotlin/org/opendc/harness/engine/discovery/DiscoverySelector.kt (renamed from simulator/opendc-experiments/opendc-experiments-sc20/src/main/kotlin/org/opendc/experiments/sc20/runner/execution/ExperimentExecutionListener.kt)27
-rw-r--r--simulator/opendc-harness/src/main/kotlin/org/opendc/harness/engine/scheduler/ExperimentScheduler.kt (renamed from simulator/opendc-experiments/opendc-experiments-sc20/src/main/kotlin/org/opendc/experiments/sc20/runner/execution/ExperimentScheduler.kt)28
-rw-r--r--simulator/opendc-harness/src/main/kotlin/org/opendc/harness/engine/scheduler/ExperimentSchedulerProvider.kt57
-rw-r--r--simulator/opendc-harness/src/main/kotlin/org/opendc/harness/engine/scheduler/ThreadPoolExperimentScheduler.kt58
-rw-r--r--simulator/opendc-harness/src/main/kotlin/org/opendc/harness/engine/scheduler/ThreadPoolExperimentSchedulerProvider.kt33
-rw-r--r--simulator/opendc-harness/src/main/kotlin/org/opendc/harness/engine/strategy/CartesianExperimentStrategy.kt55
-rw-r--r--simulator/opendc-harness/src/main/kotlin/org/opendc/harness/engine/strategy/CartesianExperimentStrategyProvider.kt32
-rw-r--r--simulator/opendc-harness/src/main/kotlin/org/opendc/harness/engine/strategy/ExperimentStrategy.kt40
-rw-r--r--simulator/opendc-harness/src/main/kotlin/org/opendc/harness/engine/strategy/ExperimentStrategyProvider.kt57
-rw-r--r--simulator/opendc-harness/src/main/kotlin/org/opendc/harness/internal/CompositeDiscovery.kt47
-rw-r--r--simulator/opendc-harness/src/main/kotlin/org/opendc/harness/internal/CompositeExperimentExecutionListener.kt57
-rw-r--r--simulator/opendc-harness/src/main/kotlin/org/opendc/harness/internal/DslDiscovery.kt101
-rw-r--r--simulator/opendc-harness/src/main/kotlin/org/opendc/harness/internal/DslDiscoveryProvider.kt36
-rw-r--r--simulator/opendc-harness/src/main/kotlin/org/opendc/harness/internal/ParameterDelegate.kt43
-rw-r--r--simulator/opendc-harness/src/main/kotlin/org/opendc/harness/internal/ScenarioImpl.kt (renamed from simulator/opendc-workflows/src/main/kotlin/org/opendc/workflows/service/stage/resource/RandomResourceSelectionPolicy.kt)32
-rw-r--r--simulator/opendc-harness/src/main/kotlin/org/opendc/harness/runner/console/ConsoleExperimentReporter.kt (renamed from simulator/opendc-experiments/opendc-experiments-sc20/src/main/kotlin/org/opendc/experiments/sc20/reporter/ConsoleExperimentReporter.kt)42
-rw-r--r--simulator/opendc-harness/src/main/kotlin/org/opendc/harness/runner/console/ConsoleRunner.kt99
-rw-r--r--simulator/opendc-harness/src/main/kotlin/org/opendc/harness/runner/junit5/JUnitExperimentExecutionListener.kt152
-rw-r--r--simulator/opendc-harness/src/main/kotlin/org/opendc/harness/runner/junit5/OpenDCTestEngine.kt94
-rw-r--r--simulator/opendc-harness/src/main/resources/META-INF/services/org.junit.platform.engine.TestEngine1
-rw-r--r--simulator/opendc-harness/src/main/resources/META-INF/services/org.opendc.harness.engine.discovery.DiscoveryProvider1
-rw-r--r--simulator/opendc-harness/src/main/resources/META-INF/services/org.opendc.harness.engine.scheduler.ExperimentSchedulerProvider1
-rw-r--r--simulator/opendc-harness/src/main/resources/META-INF/services/org.opendc.harness.engine.strategy.ExperimentStrategyProvider1
-rw-r--r--simulator/opendc-harness/src/main/resources/log4j2.xml40
-rw-r--r--simulator/opendc-harness/src/test/kotlin/org/opendc/harness/EngineTest.kt61
-rw-r--r--simulator/opendc-harness/src/test/kotlin/org/opendc/harness/TestExperiment.kt54
-rw-r--r--simulator/opendc-runner-web/src/main/kotlin/org/opendc/runner/web/Main.kt5
-rw-r--r--simulator/opendc-simulator/opendc-simulator-compute/build.gradle.kts1
-rw-r--r--simulator/opendc-simulator/opendc-simulator-compute/src/main/kotlin/org/opendc/simulator/compute/SimBareMetalMachine.kt343
-rw-r--r--simulator/opendc-simulator/opendc-simulator-compute/src/main/kotlin/org/opendc/simulator/compute/SimExecutionContext.kt113
-rw-r--r--simulator/opendc-simulator/opendc-simulator-compute/src/main/kotlin/org/opendc/simulator/compute/SimFairShareHypervisor.kt590
-rw-r--r--simulator/opendc-simulator/opendc-simulator-compute/src/main/kotlin/org/opendc/simulator/compute/SimFairShareHypervisorProvider.kt32
-rw-r--r--simulator/opendc-simulator/opendc-simulator-compute/src/main/kotlin/org/opendc/simulator/compute/SimHypervisor.kt497
-rw-r--r--simulator/opendc-simulator/opendc-simulator-compute/src/main/kotlin/org/opendc/simulator/compute/SimHypervisorProvider.kt41
-rw-r--r--simulator/opendc-simulator/opendc-simulator-compute/src/main/kotlin/org/opendc/simulator/compute/SimMachine.kt9
-rw-r--r--simulator/opendc-simulator/opendc-simulator-compute/src/main/kotlin/org/opendc/simulator/compute/SimSpaceSharedHypervisor.kt284
-rw-r--r--simulator/opendc-simulator/opendc-simulator-compute/src/main/kotlin/org/opendc/simulator/compute/SimSpaceSharedHypervisorProvider.kt32
-rw-r--r--simulator/opendc-simulator/opendc-simulator-compute/src/main/kotlin/org/opendc/simulator/compute/workload/SimFlopsWorkload.kt39
-rw-r--r--simulator/opendc-simulator/opendc-simulator-compute/src/main/kotlin/org/opendc/simulator/compute/workload/SimResourceCommand.kt52
-rw-r--r--simulator/opendc-simulator/opendc-simulator-compute/src/main/kotlin/org/opendc/simulator/compute/workload/SimRuntimeWorkload.kt60
-rw-r--r--simulator/opendc-simulator/opendc-simulator-compute/src/main/kotlin/org/opendc/simulator/compute/workload/SimTraceWorkload.kt57
-rw-r--r--simulator/opendc-simulator/opendc-simulator-compute/src/main/kotlin/org/opendc/simulator/compute/workload/SimWorkload.kt27
-rw-r--r--simulator/opendc-simulator/opendc-simulator-compute/src/main/kotlin/org/opendc/simulator/compute/workload/SimWorkloadBarrier.kt45
-rw-r--r--simulator/opendc-simulator/opendc-simulator-compute/src/test/kotlin/org/opendc/simulator/compute/SimHypervisorTest.kt117
-rw-r--r--simulator/opendc-simulator/opendc-simulator-compute/src/test/kotlin/org/opendc/simulator/compute/SimMachineTest.kt88
-rw-r--r--simulator/opendc-simulator/opendc-simulator-compute/src/test/kotlin/org/opendc/simulator/compute/SimSpaceSharedHypervisorTest.kt175
-rw-r--r--simulator/opendc-simulator/opendc-simulator-compute/src/test/kotlin/org/opendc/simulator/compute/workload/SimFlopsWorkloadTest.kt22
-rw-r--r--simulator/opendc-trace/build.gradle.kts21
-rw-r--r--simulator/opendc-trace/opendc-trace-core/build.gradle.kts (renamed from simulator/opendc-compute/opendc-compute-simulator/src/main/kotlin/org/opendc/compute/simulator/SimVirtDriverWorkload.kt)20
-rw-r--r--simulator/opendc-trace/opendc-trace-core/src/main/kotlin/org/opendc/trace/core/Event.kt (renamed from simulator/opendc-workflows/src/main/kotlin/org/opendc/workflows/service/stage/resource/FirstFitResourceSelectionPolicy.kt)18
-rw-r--r--simulator/opendc-trace/opendc-trace-core/src/main/kotlin/org/opendc/trace/core/EventStream.kt76
-rw-r--r--simulator/opendc-trace/opendc-trace-core/src/main/kotlin/org/opendc/trace/core/EventTracer.kt84
-rw-r--r--simulator/opendc-trace/opendc-trace-core/src/main/kotlin/org/opendc/trace/core/Extensions.kt73
-rw-r--r--simulator/opendc-trace/opendc-trace-core/src/main/kotlin/org/opendc/trace/core/RecordingStream.kt (renamed from simulator/opendc-experiments/opendc-experiments-sc20/src/main/kotlin/org/opendc/experiments/sc20/runner/ExperimentRunner.kt)37
-rw-r--r--simulator/opendc-trace/opendc-trace-core/src/main/kotlin/org/opendc/trace/core/internal/AbstractEventStream.kt139
-rw-r--r--simulator/opendc-trace/opendc-trace-core/src/main/kotlin/org/opendc/trace/core/internal/Dispatcher.kt77
-rw-r--r--simulator/opendc-trace/opendc-trace-core/src/main/kotlin/org/opendc/trace/core/internal/EventDispatcher.kt (renamed from simulator/opendc-experiments/opendc-experiments-sc20/src/main/kotlin/org/opendc/experiments/sc20/runner/execution/ExperimentExecutionContext.kt)29
-rw-r--r--simulator/opendc-trace/opendc-trace-core/src/main/kotlin/org/opendc/trace/core/internal/EventTracerImpl.kt157
-rw-r--r--simulator/opendc-trace/opendc-trace-core/src/main/kotlin/org/opendc/trace/core/internal/StreamState.kt (renamed from simulator/opendc-experiments/opendc-experiments-sc20/src/main/kotlin/org/opendc/experiments/sc20/runner/TrialExperimentDescriptor.kt)12
-rw-r--r--simulator/opendc-utils/build.gradle.kts5
-rw-r--r--simulator/opendc-utils/src/main/kotlin/org/opendc/utils/TimerScheduler.kt209
-rw-r--r--simulator/opendc-utils/src/test/kotlin/org/opendc/utils/TimerSchedulerTest.kt147
-rw-r--r--simulator/opendc-workflows/build.gradle.kts4
-rw-r--r--simulator/opendc-workflows/src/main/kotlin/org/opendc/workflows/service/StageWorkflowService.kt105
-rw-r--r--simulator/opendc-workflows/src/main/kotlin/org/opendc/workflows/service/TaskState.kt4
-rw-r--r--simulator/opendc-workflows/src/main/kotlin/org/opendc/workflows/service/WorkflowEvent.kt23
-rw-r--r--simulator/opendc-workflows/src/main/kotlin/org/opendc/workflows/service/WorkflowService.kt2
-rw-r--r--simulator/opendc-workflows/src/main/kotlin/org/opendc/workflows/service/stage/resource/FunctionalResourceFilterPolicy.kt41
-rw-r--r--simulator/opendc-workflows/src/main/kotlin/org/opendc/workflows/service/stage/resource/ResourceFilterPolicy.kt45
-rw-r--r--simulator/opendc-workflows/src/main/kotlin/org/opendc/workflows/workload/Metadata.kt5
-rw-r--r--simulator/opendc-workflows/src/test/kotlin/org/opendc/workflows/service/StageWorkflowSchedulerIntegrationTest.kt38
-rw-r--r--simulator/settings.gradle.kts2
142 files changed, 6135 insertions, 2417 deletions
diff --git a/simulator/buildSrc/build.gradle.kts b/simulator/buildSrc/build.gradle.kts
index 7ccd920f..6ad5cfe9 100644
--- a/simulator/buildSrc/build.gradle.kts
+++ b/simulator/buildSrc/build.gradle.kts
@@ -40,7 +40,7 @@ repositories {
}
dependencies {
- implementation(kotlin("gradle-plugin", version = "1.4.10"))
- implementation("org.jlleitschuh.gradle:ktlint-gradle:9.4.0")
+ implementation(kotlin("gradle-plugin", version = "1.4.21"))
+ implementation("org.jlleitschuh.gradle:ktlint-gradle:9.4.1")
implementation("org.jetbrains.dokka:dokka-gradle-plugin:0.10.1")
}
diff --git a/simulator/buildSrc/src/main/kotlin/kotlin-library-convention.gradle.kts b/simulator/buildSrc/src/main/kotlin/kotlin-library-convention.gradle.kts
index bbecf346..f9d8a966 100644
--- a/simulator/buildSrc/src/main/kotlin/kotlin-library-convention.gradle.kts
+++ b/simulator/buildSrc/src/main/kotlin/kotlin-library-convention.gradle.kts
@@ -33,6 +33,7 @@ plugins {
/* Project configuration */
repositories {
+ mavenCentral()
jcenter()
}
diff --git a/simulator/buildSrc/src/main/kotlin/library.kt b/simulator/buildSrc/src/main/kotlin/library.kt
index af278a07..6735c110 100644
--- a/simulator/buildSrc/src/main/kotlin/library.kt
+++ b/simulator/buildSrc/src/main/kotlin/library.kt
@@ -45,5 +45,5 @@ object Library {
/**
* Kotlin coroutines support
*/
- val KOTLINX_COROUTINES = "1.3.9"
+ val KOTLINX_COROUTINES = "1.4.2"
}
diff --git a/simulator/gradle/wrapper/gradle-wrapper.properties b/simulator/gradle/wrapper/gradle-wrapper.properties
index be52383e..da9702f9 100644
--- a/simulator/gradle/wrapper/gradle-wrapper.properties
+++ b/simulator/gradle/wrapper/gradle-wrapper.properties
@@ -1,5 +1,5 @@
distributionBase=GRADLE_USER_HOME
distributionPath=wrapper/dists
-distributionUrl=https\://services.gradle.org/distributions/gradle-6.7-bin.zip
+distributionUrl=https\://services.gradle.org/distributions/gradle-6.8-bin.zip
zipStoreBase=GRADLE_USER_HOME
zipStorePath=wrapper/dists
diff --git a/simulator/opendc-compute/opendc-compute-core/build.gradle.kts b/simulator/opendc-compute/opendc-compute-core/build.gradle.kts
index 9682b50f..ac2dc78d 100644
--- a/simulator/opendc-compute/opendc-compute-core/build.gradle.kts
+++ b/simulator/opendc-compute/opendc-compute-core/build.gradle.kts
@@ -29,6 +29,7 @@ plugins {
dependencies {
api(project(":opendc-core"))
+ api(project(":opendc-trace:opendc-trace-core"))
implementation(project(":opendc-utils"))
implementation("io.github.microutils:kotlin-logging:1.7.9")
diff --git a/simulator/opendc-compute/opendc-compute-core/src/main/kotlin/org/opendc/compute/core/virt/driver/VirtDriver.kt b/simulator/opendc-compute/opendc-compute-core/src/main/kotlin/org/opendc/compute/core/virt/driver/VirtDriver.kt
index 5ecfd357..68cc7b50 100644
--- a/simulator/opendc-compute/opendc-compute-core/src/main/kotlin/org/opendc/compute/core/virt/driver/VirtDriver.kt
+++ b/simulator/opendc-compute/opendc-compute-core/src/main/kotlin/org/opendc/compute/core/virt/driver/VirtDriver.kt
@@ -23,6 +23,7 @@
package org.opendc.compute.core.virt.driver
import kotlinx.coroutines.flow.Flow
+import org.opendc.compute.core.Flavor
import org.opendc.compute.core.Server
import org.opendc.compute.core.image.Image
import org.opendc.compute.core.virt.HypervisorEvent
@@ -40,6 +41,11 @@ public interface VirtDriver {
public val events: Flow<HypervisorEvent>
/**
+ * Determine whether the specified [flavor] can still fit on this driver.
+ */
+ public fun canFit(flavor: Flavor): Boolean
+
+ /**
* Spawn the given [Image] on the compute resource of this driver.
*
* @param name The name of the server to spawn.
@@ -50,7 +56,7 @@ public interface VirtDriver {
public suspend fun spawn(
name: String,
image: Image,
- flavor: org.opendc.compute.core.Flavor
+ flavor: Flavor
): Server
public companion object Key : AbstractServiceKey<VirtDriver>(UUID.randomUUID(), "virtual-driver")
diff --git a/simulator/opendc-compute/opendc-compute-core/src/main/kotlin/org/opendc/compute/core/virt/service/VirtProvisioningService.kt b/simulator/opendc-compute/opendc-compute-core/src/main/kotlin/org/opendc/compute/core/virt/service/VirtProvisioningService.kt
index ab96e0a3..3d722110 100644
--- a/simulator/opendc-compute/opendc-compute-core/src/main/kotlin/org/opendc/compute/core/virt/service/VirtProvisioningService.kt
+++ b/simulator/opendc-compute/opendc-compute-core/src/main/kotlin/org/opendc/compute/core/virt/service/VirtProvisioningService.kt
@@ -42,6 +42,11 @@ public interface VirtProvisioningService {
public suspend fun drivers(): Set<VirtDriver>
/**
+ * The number of hosts available in the system.
+ */
+ public val hostCount: Int
+
+ /**
* Submit the specified [Image] to the provisioning service.
*
* @param name The name of the server to deploy.
diff --git a/simulator/opendc-workflows/src/main/kotlin/org/opendc/workflows/service/stage/resource/ResourceSelectionPolicy.kt b/simulator/opendc-compute/opendc-compute-core/src/main/kotlin/org/opendc/compute/core/virt/service/events/HypervisorAvailableEvent.kt
index 990b990a..c1802e64 100644
--- a/simulator/opendc-workflows/src/main/kotlin/org/opendc/workflows/service/stage/resource/ResourceSelectionPolicy.kt
+++ b/simulator/opendc-compute/opendc-compute-core/src/main/kotlin/org/opendc/compute/core/virt/service/events/HypervisorAvailableEvent.kt
@@ -20,13 +20,12 @@
* SOFTWARE.
*/
-package org.opendc.workflows.service.stage.resource
+package org.opendc.compute.core.virt.service.events
-import org.opendc.compute.core.metal.Node
-import org.opendc.workflows.service.stage.StagePolicy
+import org.opendc.trace.core.Event
+import java.util.*
/**
- * This interface represents the **R5** stage of the Reference Architecture for Schedulers and matches the the selected
- * task with a (set of) resource(s), using policies such as First-Fit, Worst-Fit, and Best-Fit.
+ * This event is emitted when a hypervisor has become available.
*/
-public interface ResourceSelectionPolicy : StagePolicy<Comparator<Node>>
+public class HypervisorAvailableEvent(public val uid: UUID) : Event()
diff --git a/simulator/opendc-compute/opendc-compute-core/src/main/kotlin/org/opendc/compute/core/virt/service/events/HypervisorUnavailableEvent.kt b/simulator/opendc-compute/opendc-compute-core/src/main/kotlin/org/opendc/compute/core/virt/service/events/HypervisorUnavailableEvent.kt
new file mode 100644
index 00000000..1fea21ea
--- /dev/null
+++ b/simulator/opendc-compute/opendc-compute-core/src/main/kotlin/org/opendc/compute/core/virt/service/events/HypervisorUnavailableEvent.kt
@@ -0,0 +1,31 @@
+/*
+ * 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.compute.core.virt.service.events
+
+import org.opendc.trace.core.Event
+import java.util.*
+
+/**
+ * This event is emitted when a hypervisor has become unavailable.
+ */
+public class HypervisorUnavailableEvent(public val uid: UUID) : Event()
diff --git a/simulator/opendc-compute/opendc-compute-core/src/main/kotlin/org/opendc/compute/core/virt/service/events/VmScheduledEvent.kt b/simulator/opendc-compute/opendc-compute-core/src/main/kotlin/org/opendc/compute/core/virt/service/events/VmScheduledEvent.kt
new file mode 100644
index 00000000..662068dd
--- /dev/null
+++ b/simulator/opendc-compute/opendc-compute-core/src/main/kotlin/org/opendc/compute/core/virt/service/events/VmScheduledEvent.kt
@@ -0,0 +1,30 @@
+/*
+ * 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.compute.core.virt.service.events
+
+import org.opendc.trace.core.Event
+
+/**
+ * This event is emitted when a virtual machine has successfully been scheduled on a hypervisor.
+ */
+public class VmScheduledEvent(public val name: String) : Event()
diff --git a/simulator/opendc-compute/opendc-compute-core/src/main/kotlin/org/opendc/compute/core/virt/service/events/VmStoppedEvent.kt b/simulator/opendc-compute/opendc-compute-core/src/main/kotlin/org/opendc/compute/core/virt/service/events/VmStoppedEvent.kt
new file mode 100644
index 00000000..96103129
--- /dev/null
+++ b/simulator/opendc-compute/opendc-compute-core/src/main/kotlin/org/opendc/compute/core/virt/service/events/VmStoppedEvent.kt
@@ -0,0 +1,30 @@
+/*
+ * 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.compute.core.virt.service.events
+
+import org.opendc.trace.core.Event
+
+/**
+ * This event is emitted when a virtual machine has stopped running.
+ */
+public class VmStoppedEvent(public val name: String) : Event()
diff --git a/simulator/opendc-compute/opendc-compute-core/src/main/kotlin/org/opendc/compute/core/virt/service/events/VmSubmissionEvent.kt b/simulator/opendc-compute/opendc-compute-core/src/main/kotlin/org/opendc/compute/core/virt/service/events/VmSubmissionEvent.kt
new file mode 100644
index 00000000..f6b71e22
--- /dev/null
+++ b/simulator/opendc-compute/opendc-compute-core/src/main/kotlin/org/opendc/compute/core/virt/service/events/VmSubmissionEvent.kt
@@ -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.
+ */
+
+package org.opendc.compute.core.virt.service.events
+
+import org.opendc.compute.core.Flavor
+import org.opendc.compute.core.image.Image
+import org.opendc.trace.core.Event
+
+/**
+ * This event is emitted when a virtual machine is submitted to the provisioning service.
+ */
+public class VmSubmissionEvent(public val name: String, public val image: Image, public val flavor: Flavor) : Event()
diff --git a/simulator/opendc-compute/opendc-compute-core/src/main/kotlin/org/opendc/compute/core/virt/service/events/VmSubmissionInvalidEvent.kt b/simulator/opendc-compute/opendc-compute-core/src/main/kotlin/org/opendc/compute/core/virt/service/events/VmSubmissionInvalidEvent.kt
new file mode 100644
index 00000000..d0e5c102
--- /dev/null
+++ b/simulator/opendc-compute/opendc-compute-core/src/main/kotlin/org/opendc/compute/core/virt/service/events/VmSubmissionInvalidEvent.kt
@@ -0,0 +1,30 @@
+/*
+ * 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.compute.core.virt.service.events
+
+import org.opendc.trace.core.Event
+
+/**
+ * An event that is emitted when the submission is deemed to be invalid.
+ */
+public class VmSubmissionInvalidEvent(public val name: String) : Event()
diff --git a/simulator/opendc-compute/opendc-compute-simulator/build.gradle.kts b/simulator/opendc-compute/opendc-compute-simulator/build.gradle.kts
index d7570e54..dc93e956 100644
--- a/simulator/opendc-compute/opendc-compute-simulator/build.gradle.kts
+++ b/simulator/opendc-compute/opendc-compute-simulator/build.gradle.kts
@@ -29,10 +29,10 @@ plugins {
dependencies {
api(project(":opendc-compute:opendc-compute-core"))
+ api(project(":opendc-simulator:opendc-simulator-compute"))
+ api(project(":opendc-simulator:opendc-simulator-failures"))
implementation(project(":opendc-utils"))
implementation("io.github.microutils:kotlin-logging:1.7.9")
- implementation(project(":opendc-simulator:opendc-simulator-compute"))
- api(project(":opendc-simulator:opendc-simulator-failures"))
testImplementation(project(":opendc-simulator:opendc-simulator-core"))
testRuntimeOnly("org.slf4j:slf4j-simple:${Library.SLF4J}")
diff --git a/simulator/opendc-compute/opendc-compute-simulator/src/main/kotlin/org/opendc/compute/simulator/SimBareMetalDriver.kt b/simulator/opendc-compute/opendc-compute-simulator/src/main/kotlin/org/opendc/compute/simulator/SimBareMetalDriver.kt
index 97f550ba..7a978a53 100644
--- a/simulator/opendc-compute/opendc-compute-simulator/src/main/kotlin/org/opendc/compute/simulator/SimBareMetalDriver.kt
+++ b/simulator/opendc-compute/opendc-compute-simulator/src/main/kotlin/org/opendc/compute/simulator/SimBareMetalDriver.kt
@@ -41,6 +41,7 @@ import org.opendc.core.services.ServiceRegistry
import org.opendc.simulator.compute.SimBareMetalMachine
import org.opendc.simulator.compute.SimExecutionContext
import org.opendc.simulator.compute.SimMachineModel
+import org.opendc.simulator.compute.workload.SimResourceCommand
import org.opendc.simulator.compute.workload.SimWorkload
import org.opendc.simulator.failures.FailureDomain
import org.opendc.utils.flow.EventFlow
@@ -139,15 +140,31 @@ public class SimBareMetalDriver(
events
)
+ val delegate = (node.image as SimWorkloadImage).workload
// Wrap the workload to pass in a ComputeSimExecutionContext
val workload = object : SimWorkload {
- override suspend fun run(ctx: SimExecutionContext) {
- val wrappedCtx = object : ComputeSimExecutionContext, SimExecutionContext by ctx {
+ lateinit var wrappedCtx: ComputeSimExecutionContext
+
+ override fun onStart(ctx: SimExecutionContext) {
+ wrappedCtx = object : ComputeSimExecutionContext, SimExecutionContext by ctx {
override val server: Server
get() = nodeState.value.server!!
+
+ override fun toString(): String = "WrappedSimExecutionContext"
}
- (node.image as SimWorkloadImage).workload.run(wrappedCtx)
+
+ delegate.onStart(wrappedCtx)
+ }
+
+ override fun onStart(ctx: SimExecutionContext, cpu: Int): SimResourceCommand {
+ return delegate.onStart(wrappedCtx, cpu)
+ }
+
+ override fun onNext(ctx: SimExecutionContext, cpu: Int, remainingWork: Double): SimResourceCommand {
+ return delegate.onNext(wrappedCtx, cpu, remainingWork)
}
+
+ override fun toString(): String = "SimWorkloadWrapper(delegate=$delegate)"
}
job = coroutineScope.launch {
diff --git a/simulator/opendc-compute/opendc-compute-simulator/src/main/kotlin/org/opendc/compute/simulator/SimVirtDriver.kt b/simulator/opendc-compute/opendc-compute-simulator/src/main/kotlin/org/opendc/compute/simulator/SimVirtDriver.kt
index 09eec1ef..d7a8a8b2 100644
--- a/simulator/opendc-compute/opendc-compute-simulator/src/main/kotlin/org/opendc/compute/simulator/SimVirtDriver.kt
+++ b/simulator/opendc-compute/opendc-compute-simulator/src/main/kotlin/org/opendc/compute/simulator/SimVirtDriver.kt
@@ -32,30 +32,29 @@ import org.opendc.compute.core.virt.HypervisorEvent
import org.opendc.compute.core.virt.driver.InsufficientMemoryOnServerException
import org.opendc.compute.core.virt.driver.VirtDriver
import org.opendc.core.services.ServiceRegistry
-import org.opendc.simulator.compute.SimExecutionContext
-import org.opendc.simulator.compute.SimHypervisor
-import org.opendc.simulator.compute.SimMachine
+import org.opendc.simulator.compute.*
import org.opendc.simulator.compute.interference.IMAGE_PERF_INTERFERENCE_MODEL
import org.opendc.simulator.compute.interference.PerformanceInterferenceModel
+import org.opendc.simulator.compute.model.MemoryUnit
+import org.opendc.simulator.compute.workload.SimResourceCommand
import org.opendc.simulator.compute.workload.SimWorkload
import org.opendc.utils.flow.EventFlow
-import java.time.Clock
import java.util.*
/**
* A [VirtDriver] that is simulates virtual machines on a physical machine using [SimHypervisor].
*/
-public class SimVirtDriver(
- private val coroutineScope: CoroutineScope,
- clock: Clock,
- private val ctx: SimExecutionContext
-) : VirtDriver {
+public class SimVirtDriver(private val coroutineScope: CoroutineScope, hypervisor: SimHypervisorProvider) : VirtDriver, SimWorkload {
+ /**
+ * The execution context in which the [VirtDriver] runs.
+ */
+ private lateinit var ctx: ComputeSimExecutionContext
/**
* The server hosting this hypervisor.
*/
public val server: Server
- get() = (ctx as ComputeSimExecutionContext).server
+ get() = ctx.server
/**
* The [EventFlow] to emit the events.
@@ -67,35 +66,33 @@ public class SimVirtDriver(
/**
* Current total memory use of the images on this hypervisor.
*/
- private var availableMemory: Long = ctx.machine.memory.map { it.size }.sum()
+ private var availableMemory: Long = 0
/**
* The hypervisor to run multiple workloads.
*/
- private val hypervisor = SimHypervisor(
- coroutineScope,
- clock,
+ private val hypervisor = hypervisor.create(
object : SimHypervisor.Listener {
override fun onSliceFinish(
hypervisor: SimHypervisor,
- requestedBurst: Long,
- grantedBurst: Long,
- overcommissionedBurst: Long,
- interferedBurst: Long,
+ requestedWork: Long,
+ grantedWork: Long,
+ overcommittedWork: Long,
+ interferedWork: Long,
cpuUsage: Double,
cpuDemand: Double
) {
eventFlow.emit(
HypervisorEvent.SliceFinished(
this@SimVirtDriver,
- requestedBurst,
- grantedBurst,
- overcommissionedBurst,
- interferedBurst,
+ requestedWork,
+ grantedWork,
+ overcommittedWork,
+ interferedWork,
cpuUsage,
cpuDemand,
vms.size,
- (ctx as ComputeSimExecutionContext).server
+ ctx.server
)
)
}
@@ -107,6 +104,14 @@ public class SimVirtDriver(
*/
private val vms = HashSet<VirtualMachine>()
+ override fun canFit(flavor: Flavor): Boolean {
+ val sufficientMemory = availableMemory > flavor.memorySize
+ val enoughCpus = ctx.machine.cpus.size >= flavor.cpuCount
+ val canFit = hypervisor.canFit(flavor.toMachineModel())
+
+ return sufficientMemory && enoughCpus && canFit
+ }
+
override suspend fun spawn(name: String, image: Image, flavor: Flavor): Server {
val requiredMemory = flavor.memorySize
if (availableMemory - requiredMemory < 0) {
@@ -126,13 +131,26 @@ public class SimVirtDriver(
events
)
availableMemory -= requiredMemory
- val vm = VirtualMachine(server, events, hypervisor.createMachine(ctx.machine))
+
+ val vm = VirtualMachine(server, events, hypervisor.createMachine(flavor.toMachineModel()))
vms.add(vm)
vmStarted(vm)
eventFlow.emit(HypervisorEvent.VmsUpdated(this, vms.size, availableMemory))
return server
}
+ /**
+ * Convert flavor to machine model.
+ */
+ private fun Flavor.toMachineModel(): SimMachineModel {
+ val originalCpu = ctx.machine.cpus[0]
+ val processingNode = originalCpu.node.copy(coreCount = cpuCount)
+ val processingUnits = (0 until cpuCount).map { originalCpu.copy(id = it, node = processingNode) }
+ val memoryUnits = listOf(MemoryUnit("Generic", "Generic", 3200.0, memorySize))
+
+ return SimMachineModel(processingUnits, memoryUnits)
+ }
+
private fun vmStarted(vm: VirtualMachine) {
vms.forEach { it ->
vm.performanceInterferenceModel?.onStart(it.server.image.name)
@@ -148,18 +166,35 @@ public class SimVirtDriver(
/**
* A virtual machine instance that the driver manages.
*/
- private inner class VirtualMachine(server: Server, val events: EventFlow<ServerEvent>, machine: SimMachine) {
+ private inner class VirtualMachine(server: Server, val events: EventFlow<ServerEvent>, val machine: SimMachine) {
val performanceInterferenceModel: PerformanceInterferenceModel? = server.image.tags[IMAGE_PERF_INTERFERENCE_MODEL] as? PerformanceInterferenceModel?
val job = coroutineScope.launch {
+ val delegate = (server.image as SimWorkloadImage).workload
+ // Wrap the workload to pass in a ComputeSimExecutionContext
val workload = object : SimWorkload {
- override suspend fun run(ctx: SimExecutionContext) {
- val wrappedCtx = object : ComputeSimExecutionContext, SimExecutionContext by ctx {
+ lateinit var wrappedCtx: ComputeSimExecutionContext
+
+ override fun onStart(ctx: SimExecutionContext) {
+ wrappedCtx = object : ComputeSimExecutionContext, SimExecutionContext by ctx {
override val server: Server
- get() = this@VirtualMachine.server
+ get() = server
+
+ override fun toString(): String = "WrappedSimExecutionContext"
}
- (server.image as SimWorkloadImage).workload.run(wrappedCtx)
+
+ delegate.onStart(wrappedCtx)
+ }
+
+ override fun onStart(ctx: SimExecutionContext, cpu: Int): SimResourceCommand {
+ return delegate.onStart(wrappedCtx, cpu)
+ }
+
+ override fun onNext(ctx: SimExecutionContext, cpu: Int, remainingWork: Double): SimResourceCommand {
+ return delegate.onNext(wrappedCtx, cpu, remainingWork)
}
+
+ override fun toString(): String = "SimWorkloadWrapper(delegate=$delegate)"
}
delay(1) // TODO Introduce boot time
@@ -169,6 +204,8 @@ public class SimVirtDriver(
exit(null)
} catch (cause: Throwable) {
exit(cause)
+ } finally {
+ machine.close()
}
}
@@ -200,7 +237,17 @@ public class SimVirtDriver(
}
}
- public suspend fun run() {
- hypervisor.run(ctx)
+ override fun onStart(ctx: SimExecutionContext) {
+ this.ctx = ctx as ComputeSimExecutionContext
+ this.availableMemory = ctx.machine.memory.map { it.size }.sum()
+ this.hypervisor.onStart(ctx)
+ }
+
+ override fun onStart(ctx: SimExecutionContext, cpu: Int): SimResourceCommand {
+ return hypervisor.onStart(ctx, cpu)
+ }
+
+ override fun onNext(ctx: SimExecutionContext, cpu: Int, remainingWork: Double): SimResourceCommand {
+ return hypervisor.onNext(ctx, cpu, remainingWork)
}
}
diff --git a/simulator/opendc-compute/opendc-compute-simulator/src/main/kotlin/org/opendc/compute/simulator/SimVirtProvisioningService.kt b/simulator/opendc-compute/opendc-compute-simulator/src/main/kotlin/org/opendc/compute/simulator/SimVirtProvisioningService.kt
index e83370d7..defea888 100644
--- a/simulator/opendc-compute/opendc-compute-simulator/src/main/kotlin/org/opendc/compute/simulator/SimVirtProvisioningService.kt
+++ b/simulator/opendc-compute/opendc-compute-simulator/src/main/kotlin/org/opendc/compute/simulator/SimVirtProvisioningService.kt
@@ -38,7 +38,11 @@ import org.opendc.compute.core.virt.driver.InsufficientMemoryOnServerException
import org.opendc.compute.core.virt.driver.VirtDriver
import org.opendc.compute.core.virt.service.VirtProvisioningEvent
import org.opendc.compute.core.virt.service.VirtProvisioningService
+import org.opendc.compute.core.virt.service.events.*
import org.opendc.compute.simulator.allocation.AllocationPolicy
+import org.opendc.simulator.compute.SimHypervisorProvider
+import org.opendc.trace.core.EventTracer
+import org.opendc.utils.TimerScheduler
import org.opendc.utils.flow.EventFlow
import java.time.Clock
import java.util.*
@@ -51,7 +55,10 @@ public class SimVirtProvisioningService(
private val coroutineScope: CoroutineScope,
private val clock: Clock,
private val provisioningService: ProvisioningService,
- public val allocationPolicy: AllocationPolicy
+ public val allocationPolicy: AllocationPolicy,
+ private val tracer: EventTracer,
+ private val hypervisor: SimHypervisorProvider,
+ private val schedulingQuantum: Long = 300000, // 5 minutes in milliseconds
) : VirtProvisioningService {
/**
* The logger instance to use.
@@ -71,7 +78,7 @@ public class SimVirtProvisioningService(
/**
* The incoming images to be processed by the provisioner.
*/
- private val incomingImages: MutableSet<ImageView> = mutableSetOf()
+ private val incomingImages: Deque<ImageView> = ArrayDeque()
/**
* The active images in the system.
@@ -99,11 +106,16 @@ public class SimVirtProvisioningService(
override val events: Flow<VirtProvisioningEvent> = eventFlow
+ /**
+ * The [TimerScheduler] to use for scheduling the scheduler cycles.
+ */
+ private var scheduler: TimerScheduler<Unit> = TimerScheduler(coroutineScope, clock)
+
init {
coroutineScope.launch {
val provisionedNodes = provisioningService.nodes()
provisionedNodes.forEach { node ->
- val workload = SimVirtDriverWorkload()
+ val workload = SimVirtDriver(coroutineScope, hypervisor)
val hypervisorImage = SimWorkloadImage(UUID.randomUUID(), "vmm", emptyMap(), workload)
launch {
var init = false
@@ -121,7 +133,7 @@ public class SimVirtProvisioningService(
}.launchIn(this)
delay(1)
- onHypervisorAvailable(server, workload.driver)
+ onHypervisorAvailable(server, workload)
}
}
}
@@ -131,11 +143,15 @@ public class SimVirtProvisioningService(
return availableHypervisors.map { it.driver }.toSet()
}
+ override val hostCount: Int = hypervisors.size
+
override suspend fun deploy(
name: String,
image: Image,
flavor: Flavor
): Server {
+ tracer.commit(VmSubmissionEvent(name, image, flavor))
+
eventFlow.emit(
VirtProvisioningEvent.MetricsAvailable(
this@SimVirtProvisioningService,
@@ -161,36 +177,34 @@ public class SimVirtProvisioningService(
provisionedNodes.forEach { node -> provisioningService.stop(node) }
}
- private var call: Job? = null
-
private fun requestCycle() {
- if (call != null) {
+ // Bail out in case we have already requested a new cycle.
+ if (scheduler.isTimerActive(Unit)) {
return
}
- val quantum = 300000 // 5 minutes in milliseconds
// We assume that the provisioner runs at a fixed slot every time quantum (e.g t=0, t=60, t=120).
// This is important because the slices of the VMs need to be aligned.
// We calculate here the delay until the next scheduling slot.
- val delay = quantum - (clock.millis() % quantum)
+ val delay = schedulingQuantum - (clock.millis() % schedulingQuantum)
- val call = coroutineScope.launch {
- delay(delay)
- this@SimVirtProvisioningService.call = null
- schedule()
+ scheduler.startSingleTimer(Unit, delay) {
+ coroutineScope.launch { schedule() }
}
- this.call = call
}
private suspend fun schedule() {
- val imagesToBeScheduled = incomingImages.toSet()
-
- for (imageInstance in imagesToBeScheduled) {
- val requiredMemory = imageInstance.image.tags["required-memory"] as Long
+ while (incomingImages.isNotEmpty()) {
+ val imageInstance = incomingImages.peekFirst()
+ val requiredMemory = imageInstance.flavor.memorySize
val selectedHv = allocationLogic.select(availableHypervisors, imageInstance)
- if (selectedHv == null) {
+ if (selectedHv == null || !selectedHv.driver.canFit(imageInstance.flavor)) {
+ logger.trace { "Image ${imageInstance.image} selected for scheduling but no capacity available for it." }
+
if (requiredMemory > maxMemory || imageInstance.flavor.cpuCount > maxCores) {
+ tracer.commit(VmSubmissionInvalidEvent(imageInstance.name))
+
eventFlow.emit(
VirtProvisioningEvent.MetricsAvailable(
this@SimVirtProvisioningService,
@@ -199,12 +213,13 @@ public class SimVirtProvisioningService(
submittedVms,
runningVms,
finishedVms,
- queuedVms,
+ --queuedVms,
++unscheduledVms
)
)
- incomingImages -= imageInstance
+ // Remove the incoming image
+ incomingImages.poll()
logger.warn("Failed to spawn ${imageInstance.image}: does not fit [${clock.millis()}]")
continue
@@ -215,7 +230,7 @@ public class SimVirtProvisioningService(
try {
logger.info { "[${clock.millis()}] Spawning ${imageInstance.image} on ${selectedHv.server.uid} ${selectedHv.server.name} ${selectedHv.server.flavor}" }
- incomingImages -= imageInstance
+ incomingImages.poll()
// Speculatively update the hypervisor view information to prevent other images in the queue from
// deciding on stale values.
@@ -231,6 +246,8 @@ public class SimVirtProvisioningService(
imageInstance.server = server
imageInstance.continuation.resume(server)
+ tracer.commit(VmScheduledEvent(imageInstance.name))
+
eventFlow.emit(
VirtProvisioningEvent.MetricsAvailable(
this@SimVirtProvisioningService,
@@ -252,6 +269,8 @@ public class SimVirtProvisioningService(
if (event.server.state == ServerState.SHUTOFF) {
logger.info { "[${clock.millis()}] Server ${event.server.uid} ${event.server.name} ${event.server.flavor} finished." }
+ tracer.commit(VmStoppedEvent(event.server.name))
+
eventFlow.emit(
VirtProvisioningEvent.MetricsAvailable(
this@SimVirtProvisioningService,
@@ -310,6 +329,8 @@ public class SimVirtProvisioningService(
hypervisors[server] = hv
}
+ tracer.commit(HypervisorAvailableEvent(server.uid))
+
eventFlow.emit(
VirtProvisioningEvent.MetricsAvailable(
this@SimVirtProvisioningService,
@@ -333,6 +354,8 @@ public class SimVirtProvisioningService(
val hv = hypervisors[server] ?: return
availableHypervisors -= hv
+ tracer.commit(HypervisorUnavailableEvent(hv.uid))
+
eventFlow.emit(
VirtProvisioningEvent.MetricsAvailable(
this@SimVirtProvisioningService,
@@ -359,6 +382,8 @@ public class SimVirtProvisioningService(
hv.driver = hypervisor
availableHypervisors += hv
+ tracer.commit(HypervisorAvailableEvent(hv.uid))
+
eventFlow.emit(
VirtProvisioningEvent.MetricsAvailable(
this@SimVirtProvisioningService,
diff --git a/simulator/opendc-compute/opendc-compute-simulator/src/main/kotlin/org/opendc/compute/simulator/allocation/ComparableAllocationPolicyLogic.kt b/simulator/opendc-compute/opendc-compute-simulator/src/main/kotlin/org/opendc/compute/simulator/allocation/ComparableAllocationPolicyLogic.kt
index 8defe8b7..4470eab9 100644
--- a/simulator/opendc-compute/opendc-compute-simulator/src/main/kotlin/org/opendc/compute/simulator/allocation/ComparableAllocationPolicyLogic.kt
+++ b/simulator/opendc-compute/opendc-compute-simulator/src/main/kotlin/org/opendc/compute/simulator/allocation/ComparableAllocationPolicyLogic.kt
@@ -40,7 +40,7 @@ public interface ComparableAllocationPolicyLogic : AllocationPolicy.Logic {
): HypervisorView? {
return hypervisors.asSequence()
.filter { hv ->
- val fitsMemory = hv.availableMemory >= (image.image.tags["required-memory"] as Long)
+ val fitsMemory = hv.availableMemory >= (image.flavor.memorySize)
val fitsCpu = hv.server.flavor.cpuCount >= image.flavor.cpuCount
fitsMemory && fitsCpu
}
diff --git a/simulator/opendc-compute/opendc-compute-simulator/src/test/kotlin/org/opendc/compute/simulator/SimBareMetalDriverTest.kt b/simulator/opendc-compute/opendc-compute-simulator/src/test/kotlin/org/opendc/compute/simulator/SimBareMetalDriverTest.kt
index 0f1bd444..fb8a5f47 100644
--- a/simulator/opendc-compute/opendc-compute-simulator/src/test/kotlin/org/opendc/compute/simulator/SimBareMetalDriverTest.kt
+++ b/simulator/opendc-compute/opendc-compute-simulator/src/test/kotlin/org/opendc/compute/simulator/SimBareMetalDriverTest.kt
@@ -64,7 +64,7 @@ internal class SimBareMetalDriverTest {
testScope.launch {
val driver = SimBareMetalDriver(this, clock, UUID.randomUUID(), "test", emptyMap(), machineModel)
- val image = SimWorkloadImage(UUID.randomUUID(), "<unnamed>", emptyMap(), SimFlopsWorkload(4_000, 2, utilization = 1.0))
+ val image = SimWorkloadImage(UUID.randomUUID(), "<unnamed>", emptyMap(), SimFlopsWorkload(4_000, utilization = 1.0))
// Batch driver commands
withContext(coroutineContext) {
@@ -84,6 +84,6 @@ internal class SimBareMetalDriverTest {
testScope.advanceUntilIdle()
assertEquals(ServerState.SHUTOFF, finalState)
- assertEquals(1001, finalTime)
+ assertEquals(501, finalTime)
}
}
diff --git a/simulator/opendc-compute/opendc-compute-simulator/src/test/kotlin/org/opendc/compute/simulator/SimProvisioningServiceTest.kt b/simulator/opendc-compute/opendc-compute-simulator/src/test/kotlin/org/opendc/compute/simulator/SimProvisioningServiceTest.kt
index def78ce7..a33a4e5f 100644
--- a/simulator/opendc-compute/opendc-compute-simulator/src/test/kotlin/org/opendc/compute/simulator/SimProvisioningServiceTest.kt
+++ b/simulator/opendc-compute/opendc-compute-simulator/src/test/kotlin/org/opendc/compute/simulator/SimProvisioningServiceTest.kt
@@ -64,7 +64,7 @@ internal class SimProvisioningServiceTest {
val clock = DelayControllerClockAdapter(testScope)
testScope.launch {
- val image = SimWorkloadImage(UUID.randomUUID(), "<unnamed>", emptyMap(), SimFlopsWorkload(1000, 2))
+ val image = SimWorkloadImage(UUID.randomUUID(), "<unnamed>", emptyMap(), SimFlopsWorkload(1000))
val driver = SimBareMetalDriver(this, clock, UUID.randomUUID(), "test", emptyMap(), machineModel)
val provisioner = SimpleProvisioningService()
diff --git a/simulator/opendc-compute/opendc-compute-simulator/src/test/kotlin/org/opendc/compute/simulator/SimVirtDriverTest.kt b/simulator/opendc-compute/opendc-compute-simulator/src/test/kotlin/org/opendc/compute/simulator/SimVirtDriverTest.kt
index a0c61f29..1831eae0 100644
--- a/simulator/opendc-compute/opendc-compute-simulator/src/test/kotlin/org/opendc/compute/simulator/SimVirtDriverTest.kt
+++ b/simulator/opendc-compute/opendc-compute-simulator/src/test/kotlin/org/opendc/compute/simulator/SimVirtDriverTest.kt
@@ -34,6 +34,7 @@ import org.junit.jupiter.api.Test
import org.junit.jupiter.api.assertAll
import org.opendc.compute.core.Flavor
import org.opendc.compute.core.virt.HypervisorEvent
+import org.opendc.simulator.compute.SimFairShareHypervisorProvider
import org.opendc.simulator.compute.SimMachineModel
import org.opendc.simulator.compute.model.MemoryUnit
import org.opendc.simulator.compute.model.ProcessingNode
@@ -66,17 +67,17 @@ internal class SimVirtDriverTest {
}
/**
- * Test overcommissioning of a hypervisor.
+ * Test overcommitting of resources by the hypervisor.
*/
@Test
- fun overcommission() {
- var requestedBurst = 0L
- var grantedBurst = 0L
- var overcommissionedBurst = 0L
+ fun testOvercommitted() {
+ var requestedWork = 0L
+ var grantedWork = 0L
+ var overcommittedWork = 0L
scope.launch {
- val virtDriverWorkload = SimVirtDriverWorkload()
- val vmm = SimWorkloadImage(UUID.randomUUID(), "vmm", emptyMap(), virtDriverWorkload)
+ val virtDriver = SimVirtDriver(this, SimFairShareHypervisorProvider())
+ val vmm = SimWorkloadImage(UUID.randomUUID(), "vmm", emptyMap(), virtDriver)
val duration = 5 * 60L
val vmImageA = SimWorkloadImage(
UUID.randomUUID(),
@@ -84,10 +85,10 @@ internal class SimVirtDriverTest {
emptyMap(),
SimTraceWorkload(
sequenceOf(
- SimTraceWorkload.Fragment(0, 28L * duration, duration * 1000, 28.0, 2),
- SimTraceWorkload.Fragment(0, 3500L * duration, duration * 1000, 3500.0, 2),
- SimTraceWorkload.Fragment(0, 0, duration * 1000, 0.0, 2),
- SimTraceWorkload.Fragment(0, 183L * duration, duration * 1000, 183.0, 2)
+ SimTraceWorkload.Fragment(duration * 1000, 28.0, 2),
+ SimTraceWorkload.Fragment(duration * 1000, 3500.0, 2),
+ SimTraceWorkload.Fragment(duration * 1000, 0.0, 2),
+ SimTraceWorkload.Fragment(duration * 1000, 183.0, 2)
),
)
)
@@ -97,10 +98,10 @@ internal class SimVirtDriverTest {
emptyMap(),
SimTraceWorkload(
sequenceOf(
- SimTraceWorkload.Fragment(0, 28L * duration, duration * 1000, 28.0, 2),
- SimTraceWorkload.Fragment(0, 3100L * duration, duration * 1000, 3100.0, 2),
- SimTraceWorkload.Fragment(0, 0, duration * 1000, 0.0, 2),
- SimTraceWorkload.Fragment(0, 73L * duration, duration * 1000, 73.0, 2)
+ SimTraceWorkload.Fragment(duration * 1000, 28.0, 2),
+ SimTraceWorkload.Fragment(duration * 1000, 3100.0, 2),
+ SimTraceWorkload.Fragment(duration * 1000, 0.0, 2),
+ SimTraceWorkload.Fragment(duration * 1000, 73.0, 2)
)
),
)
@@ -115,31 +116,30 @@ internal class SimVirtDriverTest {
delay(5)
val flavor = Flavor(2, 0)
- val vmDriver = virtDriverWorkload.driver
- vmDriver.events
+ virtDriver.events
.onEach { event ->
when (event) {
is HypervisorEvent.SliceFinished -> {
- requestedBurst += event.requestedBurst
- grantedBurst += event.grantedBurst
- overcommissionedBurst += event.overcommissionedBurst
+ requestedWork += event.requestedBurst
+ grantedWork += event.grantedBurst
+ overcommittedWork += event.overcommissionedBurst
}
}
}
.launchIn(this)
- vmDriver.spawn("a", vmImageA, flavor)
- vmDriver.spawn("b", vmImageB, flavor)
+ virtDriver.spawn("a", vmImageA, flavor)
+ virtDriver.spawn("b", vmImageB, flavor)
}
scope.advanceUntilIdle()
assertAll(
{ assertEquals(emptyList<Throwable>(), scope.uncaughtExceptions, "No errors") },
- { assertEquals(2073600, requestedBurst, "Requested Burst does not match") },
- { assertEquals(2013600, grantedBurst, "Granted Burst does not match") },
- { assertEquals(60000, overcommissionedBurst, "Overcommissioned Burst does not match") },
- { assertEquals(1200007, scope.currentTime) }
+ { assertEquals(4197600, requestedWork, "Requested work does not match") },
+ { assertEquals(3057600, grantedWork, "Granted work does not match") },
+ { assertEquals(1140000, overcommittedWork, "Overcommitted work does not match") },
+ { assertEquals(1200006, scope.currentTime) }
)
}
}
diff --git a/simulator/opendc-experiments/opendc-experiments-sc18/build.gradle.kts b/simulator/opendc-experiments/opendc-experiments-sc18/build.gradle.kts
index 9cf72f18..b6b35694 100644
--- a/simulator/opendc-experiments/opendc-experiments-sc18/build.gradle.kts
+++ b/simulator/opendc-experiments/opendc-experiments-sc18/build.gradle.kts
@@ -29,14 +29,16 @@ plugins {
}
application {
- mainClassName = "org.opendc.experiments.sc18.TestExperiment"
+ mainClass.set("org.opendc.harness.runner.console.ConsoleRunnerKt")
}
dependencies {
api(project(":opendc-core"))
+ api(project(":opendc-harness"))
implementation(project(":opendc-format"))
implementation(project(":opendc-workflows"))
implementation(project(":opendc-simulator:opendc-simulator-core"))
+ implementation(project(":opendc-compute:opendc-compute-simulator"))
implementation("com.fasterxml.jackson.module:jackson-module-kotlin:2.9.8") {
exclude("org.jetbrains.kotlin", module = "kotlin-reflect")
}
diff --git a/simulator/opendc-experiments/opendc-experiments-sc18/src/main/kotlin/org/opendc/experiments/sc18/TestExperiment.kt b/simulator/opendc-experiments/opendc-experiments-sc18/src/main/kotlin/org/opendc/experiments/sc18/TestExperiment.kt
deleted file mode 100644
index 3786eebf..00000000
--- a/simulator/opendc-experiments/opendc-experiments-sc18/src/main/kotlin/org/opendc/experiments/sc18/TestExperiment.kt
+++ /dev/null
@@ -1,115 +0,0 @@
-/*
- * Copyright (c) 2020 AtLarge Research
- *
- * Permission is hereby granted, free of charge, to any person obtaining a copy
- * of this software and associated documentation files (the "Software"), to deal
- * in the Software without restriction, including without limitation the rights
- * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
- * copies of the Software, and to permit persons to whom the Software is
- * furnished to do so, subject to the following conditions:
- *
- * The above copyright notice and this permission notice shall be included in all
- * copies or substantial portions of the Software.
- *
- * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
- * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
- * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
- * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
- * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
- * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
- * SOFTWARE.
- */
-
-package org.opendc.experiments.sc18
-
-import kotlinx.coroutines.*
-import kotlinx.coroutines.channels.Channel
-import kotlinx.coroutines.flow.collect
-import kotlinx.coroutines.flow.onEach
-import kotlinx.coroutines.test.TestCoroutineScope
-import org.opendc.compute.core.metal.service.ProvisioningService
-import org.opendc.format.environment.sc18.Sc18EnvironmentReader
-import org.opendc.format.trace.gwf.GwfTraceReader
-import org.opendc.simulator.utils.DelayControllerClockAdapter
-import org.opendc.workflows.service.StageWorkflowService
-import org.opendc.workflows.service.WorkflowEvent
-import org.opendc.workflows.service.WorkflowSchedulerMode
-import org.opendc.workflows.service.stage.job.NullJobAdmissionPolicy
-import org.opendc.workflows.service.stage.job.SubmissionTimeJobOrderPolicy
-import org.opendc.workflows.service.stage.resource.FirstFitResourceSelectionPolicy
-import org.opendc.workflows.service.stage.resource.FunctionalResourceFilterPolicy
-import org.opendc.workflows.service.stage.task.NullTaskEligibilityPolicy
-import org.opendc.workflows.service.stage.task.SubmissionTimeTaskOrderPolicy
-import java.io.File
-import kotlin.math.max
-
-/**
- * Main entry point of the experiment.
- */
-@OptIn(ExperimentalCoroutinesApi::class)
-public fun main(args: Array<String>) {
- if (args.isEmpty()) {
- println("error: Please provide path to GWF trace")
- return
- }
-
- var total = 0
- var finished = 0
-
- val token = Channel<Boolean>()
- val testScope = TestCoroutineScope()
- val clock = DelayControllerClockAdapter(testScope)
-
- val schedulerAsync = testScope.async {
- val environment = Sc18EnvironmentReader(object {}.javaClass.getResourceAsStream("/env/setup-test.json"))
- .use { it.construct(this, clock) }
-
- StageWorkflowService(
- this,
- clock,
- environment.platforms[0].zones[0].services[ProvisioningService],
- mode = WorkflowSchedulerMode.Batch(100),
- jobAdmissionPolicy = NullJobAdmissionPolicy,
- jobOrderPolicy = SubmissionTimeJobOrderPolicy(),
- taskEligibilityPolicy = NullTaskEligibilityPolicy,
- taskOrderPolicy = SubmissionTimeTaskOrderPolicy(),
- resourceFilterPolicy = FunctionalResourceFilterPolicy,
- resourceSelectionPolicy = FirstFitResourceSelectionPolicy
- )
- }
-
- testScope.launch {
- val scheduler = schedulerAsync.await()
- scheduler.events
- .onEach { event ->
- when (event) {
- is WorkflowEvent.JobStarted -> {
- println("Job ${event.job.uid} started")
- }
- is WorkflowEvent.JobFinished -> {
- finished += 1
- println("Jobs $finished/$total finished (${event.job.tasks.size} tasks)")
-
- if (finished == total) {
- token.send(true)
- }
- }
- }
- }
- .collect()
- }
-
- testScope.launch {
- val reader = GwfTraceReader(File(args[0]))
- val scheduler = schedulerAsync.await()
-
- while (reader.hasNext()) {
- val (time, job) = reader.next()
- total += 1
- delay(max(0, time * 1000 - clock.millis()))
- scheduler.submit(job)
- }
- }
-
- testScope.advanceUntilIdle()
-}
diff --git a/simulator/opendc-experiments/opendc-experiments-sc18/src/main/kotlin/org/opendc/experiments/sc18/UnderspecificationExperiment.kt b/simulator/opendc-experiments/opendc-experiments-sc18/src/main/kotlin/org/opendc/experiments/sc18/UnderspecificationExperiment.kt
new file mode 100644
index 00000000..6d2c0ec7
--- /dev/null
+++ b/simulator/opendc-experiments/opendc-experiments-sc18/src/main/kotlin/org/opendc/experiments/sc18/UnderspecificationExperiment.kt
@@ -0,0 +1,135 @@
+/*
+ * 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.experiments.sc18
+
+import kotlinx.coroutines.*
+import kotlinx.coroutines.test.TestCoroutineScope
+import org.opendc.compute.core.metal.service.ProvisioningService
+import org.opendc.compute.simulator.SimVirtProvisioningService
+import org.opendc.compute.simulator.allocation.NumberOfActiveServersAllocationPolicy
+import org.opendc.format.environment.sc18.Sc18EnvironmentReader
+import org.opendc.format.trace.gwf.GwfTraceReader
+import org.opendc.harness.dsl.Experiment
+import org.opendc.harness.dsl.anyOf
+import org.opendc.simulator.compute.SimSpaceSharedHypervisorProvider
+import org.opendc.simulator.utils.DelayControllerClockAdapter
+import org.opendc.trace.core.EventTracer
+import org.opendc.trace.core.enable
+import org.opendc.workflows.service.StageWorkflowService
+import org.opendc.workflows.service.WorkflowEvent
+import org.opendc.workflows.service.WorkflowSchedulerMode
+import org.opendc.workflows.service.stage.job.NullJobAdmissionPolicy
+import org.opendc.workflows.service.stage.job.SubmissionTimeJobOrderPolicy
+import org.opendc.workflows.service.stage.task.NullTaskEligibilityPolicy
+import org.opendc.workflows.service.stage.task.SubmissionTimeTaskOrderPolicy
+import java.io.File
+import java.io.FileInputStream
+import kotlin.math.max
+
+/**
+ * The [UnderspecificationExperiment] investigates the impact of scheduler underspecification on performance.
+ * It focuses on components that must exist (that is, based on their own publications, the correct operation of the
+ * schedulers under study requires these components), yet have been left underspecified by their author.
+ */
+public class UnderspecificationExperiment : Experiment("underspecification") {
+ /**
+ * The workflow traces to test.
+ */
+ private val trace: String by anyOf("traces/chronos_exp_noscaler_ca.gwf")
+
+ /**
+ * The datacenter environments to test.
+ */
+ private val environment: String by anyOf("environments/base.json")
+
+ @OptIn(ExperimentalCoroutinesApi::class)
+ override fun doRun(repeat: Int) {
+ val testScope = TestCoroutineScope()
+ val clock = DelayControllerClockAdapter(testScope)
+ val tracer = EventTracer(clock)
+ val recording = tracer.openRecording().run {
+ enable<WorkflowEvent.JobSubmitted>()
+ enable<WorkflowEvent.JobStarted>()
+ enable<WorkflowEvent.JobFinished>()
+ enable<WorkflowEvent.TaskStarted>()
+ enable<WorkflowEvent.TaskFinished>()
+ this
+ }
+
+ testScope.launch {
+ launch { println("MAKESPAN: ${recording.workflowRuntime()}") }
+ launch { println("WAIT: ${recording.workflowWaitingTime()}") }
+ recording.start()
+ }
+
+ testScope.launch {
+ val environment = Sc18EnvironmentReader(FileInputStream(File(environment)))
+ .use { it.construct(testScope, clock) }
+
+ val bareMetal = environment.platforms[0].zones[0].services[ProvisioningService]
+
+ // Wait for the bare metal nodes to be spawned
+ delay(10)
+
+ val provisioner = SimVirtProvisioningService(
+ testScope,
+ clock,
+ bareMetal,
+ NumberOfActiveServersAllocationPolicy(),
+ tracer,
+ SimSpaceSharedHypervisorProvider(),
+ schedulingQuantum = 1000
+ )
+
+ // Wait for the hypervisors to be spawned
+ delay(10)
+
+ val scheduler = StageWorkflowService(
+ testScope,
+ clock,
+ tracer,
+ provisioner,
+ mode = WorkflowSchedulerMode.Batch(100),
+ jobAdmissionPolicy = NullJobAdmissionPolicy,
+ jobOrderPolicy = SubmissionTimeJobOrderPolicy(),
+ taskEligibilityPolicy = NullTaskEligibilityPolicy,
+ taskOrderPolicy = SubmissionTimeTaskOrderPolicy(),
+ )
+
+ val reader = GwfTraceReader(File(trace))
+
+ while (reader.hasNext()) {
+ val (time, job) = reader.next()
+ delay(max(0, time * 1000 - clock.millis()))
+ scheduler.submit(job)
+ }
+ }
+
+ testScope.advanceUntilIdle()
+ recording.close()
+
+ // Check whether everything went okay
+ testScope.uncaughtExceptions.forEach { it.printStackTrace() }
+ assert(testScope.uncaughtExceptions.isEmpty()) { "Errors occurred during execution of the experiment" }
+ }
+}
diff --git a/simulator/opendc-experiments/opendc-experiments-sc18/src/main/kotlin/org/opendc/experiments/sc18/WorkflowMetrics.kt b/simulator/opendc-experiments/opendc-experiments-sc18/src/main/kotlin/org/opendc/experiments/sc18/WorkflowMetrics.kt
new file mode 100644
index 00000000..dbd04b87
--- /dev/null
+++ b/simulator/opendc-experiments/opendc-experiments-sc18/src/main/kotlin/org/opendc/experiments/sc18/WorkflowMetrics.kt
@@ -0,0 +1,86 @@
+/*
+ * 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.experiments.sc18
+
+import org.opendc.trace.core.EventStream
+import org.opendc.trace.core.onEvent
+import org.opendc.workflows.service.WorkflowEvent
+import java.util.*
+import kotlin.coroutines.resume
+import kotlin.coroutines.suspendCoroutine
+
+/**
+ * This function collects the makespan of workflows that appear in the event stream.
+ */
+public suspend fun EventStream.workflowRuntime(): Map<UUID, Long> = suspendCoroutine { cont ->
+ val starts = mutableMapOf<UUID, Long>()
+ val results = mutableMapOf<UUID, Long>()
+
+ onEvent<WorkflowEvent.JobStarted> {
+ starts[it.job.uid] = it.timestamp
+ }
+ onEvent<WorkflowEvent.JobFinished> {
+ val start = starts.remove(it.job.uid) ?: return@onEvent
+ results[it.job.uid] = it.timestamp - start
+ }
+ onClose { cont.resume(results) }
+}
+
+/**
+ * This function collects the waiting time of workflows that appear in the event stream, which the duration between the
+ * workflow submission and the start of the first task.
+ */
+public suspend fun EventStream.workflowWaitingTime(): Map<UUID, Long> = suspendCoroutine { cont ->
+ val starts = mutableMapOf<UUID, Long>()
+ val results = mutableMapOf<UUID, Long>()
+
+ onEvent<WorkflowEvent.JobStarted> {
+ starts[it.job.uid] = it.timestamp
+ }
+ onEvent<WorkflowEvent.TaskStarted> {
+ results.computeIfAbsent(it.job.uid) { _ ->
+ val start = starts.remove(it.job.uid)!!
+ it.timestamp - start
+ }
+ }
+ onClose { cont.resume(results) }
+}
+
+/**
+ * This function collects the response time of tasks that appear in the event stream.
+ */
+public suspend fun EventStream.taskResponse(): Map<UUID, Long> = suspendCoroutine { cont ->
+ val starts = mutableMapOf<UUID, Long>()
+ val results = mutableMapOf<UUID, Long>()
+
+ onEvent<WorkflowEvent.JobSubmitted> {
+ for (task in it.job.tasks) {
+ starts[task.uid] = it.timestamp
+ }
+ }
+ onEvent<WorkflowEvent.TaskFinished> {
+ val start = starts.remove(it.job.uid) ?: return@onEvent
+ results[it.task.uid] = it.timestamp - start
+ }
+ onClose { cont.resume(results) }
+}
diff --git a/simulator/opendc-experiments/opendc-experiments-sc18/src/main/resources/env/setup-test.json b/simulator/opendc-experiments/opendc-experiments-sc18/src/main/resources/env/setup-test.json
deleted file mode 100644
index 0965b250..00000000
--- a/simulator/opendc-experiments/opendc-experiments-sc18/src/main/resources/env/setup-test.json
+++ /dev/null
@@ -1,36 +0,0 @@
-{
- "name": "Experimental Setup 2",
- "rooms": [
- {
- "type": "SERVER",
- "objects": [
- {
- "type": "RACK",
- "machines": [
- { "cpus": [2] }, { "cpus": [2]},
- { "cpus": [2] }, { "cpus": [2]},
- { "cpus": [2] }, { "cpus": [2]},
- { "cpus": [2] }, { "cpus": [2]},
- { "cpus": [2] }, { "cpus": [2]},
- { "cpus": [2] }, { "cpus": [2]},
- { "cpus": [2] }, { "cpus": [2]},
- { "cpus": [2] }, { "cpus": [2]}
- ]
- },
- {
- "type": "RACK",
- "machines": [
- { "cpus": [1] }, { "cpus": [1]},
- { "cpus": [1] }, { "cpus": [1]},
- { "cpus": [1] }, { "cpus": [1]},
- { "cpus": [1] }, { "cpus": [1]},
- { "cpus": [1] }, { "cpus": [1]},
- { "cpus": [1] }, { "cpus": [1]},
- { "cpus": [1] }, { "cpus": [1]},
- { "cpus": [1] }, { "cpus": [1]}
- ]
- }
- ]
- }
- ]
-}
diff --git a/simulator/opendc-experiments/opendc-experiments-sc20/build.gradle.kts b/simulator/opendc-experiments/opendc-experiments-sc20/build.gradle.kts
index 3b682668..b94207ba 100644
--- a/simulator/opendc-experiments/opendc-experiments-sc20/build.gradle.kts
+++ b/simulator/opendc-experiments/opendc-experiments-sc20/build.gradle.kts
@@ -29,21 +29,22 @@ plugins {
}
application {
- mainClassName = "org.opendc.experiments.sc20.MainKt"
+ mainClass.set("org.opendc.harness.runner.console.ConsoleRunnerKt")
applicationDefaultJvmArgs = listOf("-Xms2500M")
}
dependencies {
api(project(":opendc-core"))
+ api(project(":opendc-harness"))
implementation(project(":opendc-format"))
implementation(project(":opendc-simulator:opendc-simulator-core"))
implementation(project(":opendc-simulator:opendc-simulator-compute"))
implementation(project(":opendc-simulator:opendc-simulator-failures"))
implementation(project(":opendc-compute:opendc-compute-simulator"))
- implementation("com.github.ajalt:clikt:2.6.0")
- implementation("me.tongfei:progressbar:0.8.1")
- implementation("io.github.microutils:kotlin-logging:1.7.9")
+ implementation("io.github.microutils:kotlin-logging:2.0.4")
+ implementation("me.tongfei:progressbar:0.9.0")
+ implementation("com.github.ajalt.clikt:clikt:3.1.0")
implementation("org.apache.parquet:parquet-avro:1.11.0")
implementation("org.apache.hadoop:hadoop-client:3.2.1") {
@@ -51,8 +52,6 @@ dependencies {
exclude(group = "log4j")
}
- runtimeOnly("org.apache.logging.log4j:log4j-slf4j-impl:2.13.1")
-
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-experiments/opendc-experiments-sc20/src/main/kotlin/org/opendc/experiments/sc20/Main.kt b/simulator/opendc-experiments/opendc-experiments-sc20/src/main/kotlin/org/opendc/experiments/sc20/Main.kt
deleted file mode 100644
index 8916261b..00000000
--- a/simulator/opendc-experiments/opendc-experiments-sc20/src/main/kotlin/org/opendc/experiments/sc20/Main.kt
+++ /dev/null
@@ -1,159 +0,0 @@
-/*
- * Copyright (c) 2020 AtLarge Research
- *
- * Permission is hereby granted, free of charge, to any person obtaining a copy
- * of this software and associated documentation files (the "Software"), to deal
- * in the Software without restriction, including without limitation the rights
- * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
- * copies of the Software, and to permit persons to whom the Software is
- * furnished to do so, subject to the following conditions:
- *
- * The above copyright notice and this permission notice shall be included in all
- * copies or substantial portions of the Software.
- *
- * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
- * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
- * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
- * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
- * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
- * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
- * SOFTWARE.
- */
-
-package org.opendc.experiments.sc20
-
-import com.github.ajalt.clikt.core.CliktCommand
-import com.github.ajalt.clikt.parameters.options.convert
-import com.github.ajalt.clikt.parameters.options.default
-import com.github.ajalt.clikt.parameters.options.defaultLazy
-import com.github.ajalt.clikt.parameters.options.multiple
-import com.github.ajalt.clikt.parameters.options.option
-import com.github.ajalt.clikt.parameters.options.required
-import com.github.ajalt.clikt.parameters.types.choice
-import com.github.ajalt.clikt.parameters.types.file
-import com.github.ajalt.clikt.parameters.types.int
-import mu.KotlinLogging
-import org.opendc.experiments.sc20.experiment.*
-import org.opendc.experiments.sc20.reporter.ConsoleExperimentReporter
-import org.opendc.experiments.sc20.runner.ExperimentDescriptor
-import org.opendc.experiments.sc20.runner.execution.ThreadPoolExperimentScheduler
-import org.opendc.experiments.sc20.runner.internal.DefaultExperimentRunner
-import org.opendc.format.trace.sc20.Sc20PerformanceInterferenceReader
-import org.opendc.format.trace.sc20.Sc20VmPlacementReader
-import java.io.File
-
-/**
- * The logger for this experiment.
- */
-private val logger = KotlinLogging.logger {}
-
-/**
- * Represents the command for running the experiment.
- */
-public class ExperimentCli : CliktCommand(name = "sc20-experiment") {
- /**
- * The path to the directory where the topology descriptions are located.
- */
- private val environmentPath by option("--environment-path", help = "path to the environment directory")
- .file(canBeFile = false)
- .required()
-
- /**
- * The path to the directory where the traces are located.
- */
- private val tracePath by option("--trace-path", help = "path to the traces directory")
- .file(canBeFile = false)
- .required()
-
- /**
- * The path to the performance interference model.
- */
- private val performanceInterferenceStream by option(
- "--performance-interference-model",
- help = "path to the performance interference file"
- )
- .file(canBeDir = false)
- .convert { it.inputStream() }
-
- /**
- * The path to the original VM placements file.
- */
- private val vmPlacements by option("--vm-placements-file", help = "path to the VM placement file")
- .file(canBeDir = false)
- .convert {
- Sc20VmPlacementReader(it.inputStream().buffered()).construct()
- }
- .default(emptyMap())
-
- /**
- * The selected portfolios to run.
- */
- private val portfolios by option("--portfolio", help = "portfolio of scenarios to explore")
- .choice(
- "hor-ver" to { experiment: Experiment, i: Int -> HorVerPortfolio(experiment, i) }
- as (Experiment, Int) -> Portfolio,
- "more-velocity" to { experiment, i -> MoreVelocityPortfolio(experiment, i) },
- "composite-workload" to { experiment, i -> CompositeWorkloadPortfolio(experiment, i) },
- "operational-phenomena" to { experiment, i -> OperationalPhenomenaPortfolio(experiment, i) },
- "replay" to { experiment, i -> ReplayPortfolio(experiment, i) },
- "test" to { experiment, i -> TestPortfolio(experiment, i) },
- "more-hpc" to { experiment, i -> MoreHpcPortfolio(experiment, i) },
- ignoreCase = true
- )
- .multiple(required = true)
-
- /**
- * The maximum number of worker threads to use.
- */
- private val parallelism by option("--parallelism", help = "maximum number of concurrent simulation runs")
- .int()
- .default(Runtime.getRuntime().availableProcessors())
-
- /**
- * The buffer size for writing results.
- */
- private val bufferSize by option("--buffer-size")
- .int()
- .default(4096)
-
- /**
- * The path to the output directory.
- */
- private val output by option("-O", "--output", help = "path to the output directory")
- .file(canBeFile = false)
- .defaultLazy { File("data") }
-
- override fun run() {
- logger.info { "Constructing performance interference model" }
-
- val performanceInterferenceModel =
- performanceInterferenceStream?.let { Sc20PerformanceInterferenceReader(it) }
-
- logger.info { "Creating experiment descriptor" }
- val descriptor = object :
- Experiment(environmentPath, tracePath, output, performanceInterferenceModel, vmPlacements, bufferSize) {
- private val descriptor = this
- override val children: Sequence<ExperimentDescriptor> = sequence {
- for ((i, producer) in portfolios.withIndex()) {
- yield(producer(descriptor, i))
- }
- }
- }
-
- logger.info { "Starting experiment runner [parallelism=$parallelism]" }
- val scheduler = ThreadPoolExperimentScheduler(parallelism)
- val runner = DefaultExperimentRunner(scheduler)
- val reporter = ConsoleExperimentReporter()
- try {
- runner.execute(descriptor, reporter)
- } finally {
- scheduler.close()
- reporter.close()
- }
- }
-}
-
-/**
- * Main entry point of the experiment.
- */
-public fun main(args: Array<String>): Unit = ExperimentCli().main(args)
diff --git a/simulator/opendc-experiments/opendc-experiments-sc20/src/main/kotlin/org/opendc/experiments/sc20/experiment/CompositeWorkloadPortfolio.kt b/simulator/opendc-experiments/opendc-experiments-sc20/src/main/kotlin/org/opendc/experiments/sc20/experiment/CompositeWorkloadPortfolio.kt
new file mode 100644
index 00000000..f4242456
--- /dev/null
+++ b/simulator/opendc-experiments/opendc-experiments-sc20/src/main/kotlin/org/opendc/experiments/sc20/experiment/CompositeWorkloadPortfolio.kt
@@ -0,0 +1,79 @@
+/*
+ * 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.experiments.sc20.experiment
+
+import org.opendc.experiments.sc20.experiment.model.CompositeWorkload
+import org.opendc.experiments.sc20.experiment.model.OperationalPhenomena
+import org.opendc.experiments.sc20.experiment.model.Topology
+import org.opendc.experiments.sc20.experiment.model.Workload
+import org.opendc.harness.dsl.anyOf
+
+/**
+ * A [Portfolio] that explores the effect of a composite workload.
+ */
+public class CompositeWorkloadPortfolio : Portfolio("composite-workload") {
+ private val totalSampleLoad = 1.3301733005049648E12
+
+ override val topology: Topology by anyOf(
+ Topology("base"),
+ Topology("exp-vol-hor-hom"),
+ Topology("exp-vol-ver-hom"),
+ Topology("exp-vel-ver-hom")
+ )
+
+ override val workload: Workload by anyOf(
+ CompositeWorkload(
+ "all-azure",
+ listOf(Workload("solvinity-short", 0.0), Workload("azure", 1.0)),
+ totalSampleLoad
+ ),
+ CompositeWorkload(
+ "solvinity-25-azure-75",
+ listOf(Workload("solvinity-short", 0.25), Workload("azure", 0.75)),
+ totalSampleLoad
+ ),
+ CompositeWorkload(
+ "solvinity-50-azure-50",
+ listOf(Workload("solvinity-short", 0.5), Workload("azure", 0.5)),
+ totalSampleLoad
+ ),
+ CompositeWorkload(
+ "solvinity-75-azure-25",
+ listOf(Workload("solvinity-short", 0.75), Workload("azure", 0.25)),
+ totalSampleLoad
+ ),
+ CompositeWorkload(
+ "all-solvinity",
+ listOf(Workload("solvinity-short", 1.0), Workload("azure", 0.0)),
+ totalSampleLoad
+ )
+ )
+
+ override val operationalPhenomena: OperationalPhenomena by anyOf(
+ OperationalPhenomena(failureFrequency = 24.0 * 7, hasInterference = false)
+ )
+
+ override val allocationPolicy: String by anyOf(
+ "active-servers"
+ )
+}
diff --git a/simulator/opendc-experiments/opendc-experiments-sc20/src/main/kotlin/org/opendc/experiments/sc20/experiment/Experiment.kt b/simulator/opendc-experiments/opendc-experiments-sc20/src/main/kotlin/org/opendc/experiments/sc20/experiment/Experiment.kt
deleted file mode 100644
index 34d7301b..00000000
--- a/simulator/opendc-experiments/opendc-experiments-sc20/src/main/kotlin/org/opendc/experiments/sc20/experiment/Experiment.kt
+++ /dev/null
@@ -1,76 +0,0 @@
-/*
- * Copyright (c) 2020 AtLarge Research
- *
- * Permission is hereby granted, free of charge, to any person obtaining a copy
- * of this software and associated documentation files (the "Software"), to deal
- * in the Software without restriction, including without limitation the rights
- * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
- * copies of the Software, and to permit persons to whom the Software is
- * furnished to do so, subject to the following conditions:
- *
- * The above copyright notice and this permission notice shall be included in all
- * copies or substantial portions of the Software.
- *
- * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
- * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
- * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
- * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
- * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
- * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
- * SOFTWARE.
- */
-
-package org.opendc.experiments.sc20.experiment
-
-import org.opendc.experiments.sc20.runner.ContainerExperimentDescriptor
-import org.opendc.experiments.sc20.runner.ExperimentDescriptor
-import org.opendc.experiments.sc20.runner.execution.ExperimentExecutionContext
-import org.opendc.experiments.sc20.runner.execution.ExperimentExecutionListener
-import org.opendc.experiments.sc20.telemetry.RunEvent
-import org.opendc.experiments.sc20.telemetry.parquet.ParquetRunEventWriter
-import org.opendc.format.trace.PerformanceInterferenceModelReader
-import java.io.File
-
-/**
- * The global configuration of the experiment.
- *
- * @param environments The path to the topologies directory.
- * @param traces The path to the traces directory.
- * @param output The output directory.
- * @param performanceInterferenceModel The optional performance interference model that has been specified.
- * @param vmPlacements Original VM placement in the trace.
- * @param bufferSize The buffer size of the event reporters.
- */
-public abstract class Experiment(
- public val environments: File,
- public val traces: File,
- public val output: File,
- public val performanceInterferenceModel: PerformanceInterferenceModelReader?,
- public val vmPlacements: Map<String, String>,
- public val bufferSize: Int
-) : ContainerExperimentDescriptor() {
- override val parent: ExperimentDescriptor? = null
-
- override suspend fun invoke(context: ExperimentExecutionContext) {
- val writer = ParquetRunEventWriter(File(output, "experiments.parquet"), bufferSize)
- try {
- val listener = object : ExperimentExecutionListener by context.listener {
- override fun descriptorRegistered(descriptor: ExperimentDescriptor) {
- if (descriptor is Run) {
- writer.write(RunEvent(descriptor, System.currentTimeMillis()))
- }
-
- context.listener.descriptorRegistered(descriptor)
- }
- }
-
- val newContext = object : ExperimentExecutionContext by context {
- override val listener: ExperimentExecutionListener = listener
- }
-
- super.invoke(newContext)
- } finally {
- writer.close()
- }
- }
-}
diff --git a/simulator/opendc-experiments/opendc-experiments-sc20/src/main/kotlin/org/opendc/experiments/sc20/experiment/ExperimentHelpers.kt b/simulator/opendc-experiments/opendc-experiments-sc20/src/main/kotlin/org/opendc/experiments/sc20/experiment/ExperimentHelpers.kt
index 09f44199..1e01e892 100644
--- a/simulator/opendc-experiments/opendc-experiments-sc20/src/main/kotlin/org/opendc/experiments/sc20/experiment/ExperimentHelpers.kt
+++ b/simulator/opendc-experiments/opendc-experiments-sc20/src/main/kotlin/org/opendc/experiments/sc20/experiment/ExperimentHelpers.kt
@@ -48,10 +48,12 @@ import org.opendc.experiments.sc20.experiment.monitor.ExperimentMonitor
import org.opendc.experiments.sc20.trace.Sc20StreamingParquetTraceReader
import org.opendc.format.environment.EnvironmentReader
import org.opendc.format.trace.TraceReader
+import org.opendc.simulator.compute.SimFairShareHypervisorProvider
import org.opendc.simulator.compute.interference.PerformanceInterferenceModel
import org.opendc.simulator.failures.CorrelatedFaultInjector
import org.opendc.simulator.failures.FailureDomain
import org.opendc.simulator.failures.FaultInjector
+import org.opendc.trace.core.EventTracer
import java.io.File
import java.time.Clock
import kotlin.math.ln
@@ -140,7 +142,8 @@ public suspend fun createProvisioner(
coroutineScope: CoroutineScope,
clock: Clock,
environmentReader: EnvironmentReader,
- allocationPolicy: AllocationPolicy
+ allocationPolicy: AllocationPolicy,
+ eventTracer: EventTracer
): Pair<ProvisioningService, SimVirtProvisioningService> {
val environment = environmentReader.use { it.construct(coroutineScope, clock) }
val bareMetalProvisioner = environment.platforms[0].zones[0].services[ProvisioningService]
@@ -148,7 +151,7 @@ public suspend fun createProvisioner(
// Wait for the bare metal nodes to be spawned
delay(10)
- val scheduler = SimVirtProvisioningService(coroutineScope, clock, bareMetalProvisioner, allocationPolicy)
+ val scheduler = SimVirtProvisioningService(coroutineScope, clock, bareMetalProvisioner, allocationPolicy, eventTracer, SimFairShareHypervisorProvider())
// Wait for the hypervisors to be spawned
delay(10)
diff --git a/simulator/opendc-experiments/opendc-experiments-sc20/src/main/kotlin/org/opendc/experiments/sc20/experiment/HorVerPortfolio.kt b/simulator/opendc-experiments/opendc-experiments-sc20/src/main/kotlin/org/opendc/experiments/sc20/experiment/HorVerPortfolio.kt
new file mode 100644
index 00000000..aa97b808
--- /dev/null
+++ b/simulator/opendc-experiments/opendc-experiments-sc20/src/main/kotlin/org/opendc/experiments/sc20/experiment/HorVerPortfolio.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.experiments.sc20.experiment
+
+import org.opendc.experiments.sc20.experiment.model.OperationalPhenomena
+import org.opendc.experiments.sc20.experiment.model.Topology
+import org.opendc.experiments.sc20.experiment.model.Workload
+import org.opendc.harness.dsl.anyOf
+
+/**
+ * A [Portfolio] that explores the difference between horizontal and vertical scaling.
+ */
+public class HorVerPortfolio : Portfolio("horizontal_vs_vertical") {
+ override val topology: Topology by anyOf(
+ Topology("base"),
+ Topology("rep-vol-hor-hom"),
+ Topology("rep-vol-hor-het"),
+ Topology("rep-vol-ver-hom"),
+ Topology("rep-vol-ver-het"),
+ Topology("exp-vol-hor-hom"),
+ Topology("exp-vol-hor-het"),
+ Topology("exp-vol-ver-hom"),
+ Topology("exp-vol-ver-het")
+ )
+
+ override val workload: Workload by anyOf(
+ Workload("solvinity", 0.1),
+ Workload("solvinity", 0.25),
+ Workload("solvinity", 0.5),
+ Workload("solvinity", 1.0)
+ )
+
+ override val operationalPhenomena: OperationalPhenomena by anyOf(
+ OperationalPhenomena(failureFrequency = 24.0 * 7, hasInterference = true)
+ )
+
+ override val allocationPolicy: String by anyOf(
+ "active-servers"
+ )
+}
diff --git a/simulator/opendc-experiments/opendc-experiments-sc20/src/main/kotlin/org/opendc/experiments/sc20/experiment/MoreHpcPortfolio.kt b/simulator/opendc-experiments/opendc-experiments-sc20/src/main/kotlin/org/opendc/experiments/sc20/experiment/MoreHpcPortfolio.kt
new file mode 100644
index 00000000..bdb33f59
--- /dev/null
+++ b/simulator/opendc-experiments/opendc-experiments-sc20/src/main/kotlin/org/opendc/experiments/sc20/experiment/MoreHpcPortfolio.kt
@@ -0,0 +1,56 @@
+/*
+ * 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.experiments.sc20.experiment
+
+import org.opendc.experiments.sc20.experiment.model.*
+import org.opendc.harness.dsl.anyOf
+
+/**
+ * A [Portfolio] to explore the effect of HPC workloads.
+ */
+public class MoreHpcPortfolio : Portfolio("more_hpc") {
+ override val topology: Topology by anyOf(
+ Topology("base"),
+ Topology("exp-vol-hor-hom"),
+ Topology("exp-vol-ver-hom"),
+ Topology("exp-vel-ver-hom")
+ )
+
+ override val workload: Workload by anyOf(
+ Workload("solvinity", 0.0, samplingStrategy = SamplingStrategy.HPC),
+ Workload("solvinity", 0.25, samplingStrategy = SamplingStrategy.HPC),
+ Workload("solvinity", 0.5, samplingStrategy = SamplingStrategy.HPC),
+ Workload("solvinity", 1.0, samplingStrategy = SamplingStrategy.HPC),
+ Workload("solvinity", 0.25, samplingStrategy = SamplingStrategy.HPC_LOAD),
+ Workload("solvinity", 0.5, samplingStrategy = SamplingStrategy.HPC_LOAD),
+ Workload("solvinity", 1.0, samplingStrategy = SamplingStrategy.HPC_LOAD)
+ )
+
+ override val operationalPhenomena: OperationalPhenomena by anyOf(
+ OperationalPhenomena(failureFrequency = 24.0 * 7, hasInterference = true)
+ )
+
+ override val allocationPolicy: String by anyOf(
+ "active-servers"
+ )
+}
diff --git a/simulator/opendc-experiments/opendc-experiments-sc20/src/main/kotlin/org/opendc/experiments/sc20/experiment/MoreVelocityPortfolio.kt b/simulator/opendc-experiments/opendc-experiments-sc20/src/main/kotlin/org/opendc/experiments/sc20/experiment/MoreVelocityPortfolio.kt
new file mode 100644
index 00000000..733dabf6
--- /dev/null
+++ b/simulator/opendc-experiments/opendc-experiments-sc20/src/main/kotlin/org/opendc/experiments/sc20/experiment/MoreVelocityPortfolio.kt
@@ -0,0 +1,56 @@
+/*
+ * Copyright (c) 2021 AtLarge Research
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
+ * of this software and associated documentation files (the "Software"), to deal
+ * in the Software without restriction, including without limitation the rights
+ * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+ * copies of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included in all
+ * copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
+ * SOFTWARE.
+ */
+
+package org.opendc.experiments.sc20.experiment
+
+import org.opendc.experiments.sc20.experiment.model.OperationalPhenomena
+import org.opendc.experiments.sc20.experiment.model.Topology
+import org.opendc.experiments.sc20.experiment.model.Workload
+import org.opendc.harness.dsl.anyOf
+
+/**
+ * A [Portfolio] that explores the effect of adding more velocity to a cluster (e.g., faster machines).
+ */
+public class MoreVelocityPortfolio : Portfolio("more_velocity") {
+ override val topology: Topology by anyOf(
+ Topology("base"),
+ Topology("rep-vel-ver-hom"),
+ Topology("rep-vel-ver-het"),
+ Topology("exp-vel-ver-hom"),
+ Topology("exp-vel-ver-het")
+ )
+
+ override val workload: Workload by anyOf(
+ Workload("solvinity", 0.1),
+ Workload("solvinity", 0.25),
+ Workload("solvinity", 0.5),
+ Workload("solvinity", 1.0)
+ )
+
+ override val operationalPhenomena: OperationalPhenomena by anyOf(
+ OperationalPhenomena(failureFrequency = 24.0 * 7, hasInterference = true)
+ )
+
+ override val allocationPolicy: String by anyOf(
+ "active-servers"
+ )
+}
diff --git a/simulator/opendc-experiments/opendc-experiments-sc20/src/main/kotlin/org/opendc/experiments/sc20/experiment/OperationalPhenomenaPortfolio.kt b/simulator/opendc-experiments/opendc-experiments-sc20/src/main/kotlin/org/opendc/experiments/sc20/experiment/OperationalPhenomenaPortfolio.kt
new file mode 100644
index 00000000..66b94faf
--- /dev/null
+++ b/simulator/opendc-experiments/opendc-experiments-sc20/src/main/kotlin/org/opendc/experiments/sc20/experiment/OperationalPhenomenaPortfolio.kt
@@ -0,0 +1,61 @@
+/*
+ * 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.experiments.sc20.experiment
+
+import org.opendc.experiments.sc20.experiment.model.OperationalPhenomena
+import org.opendc.experiments.sc20.experiment.model.Topology
+import org.opendc.experiments.sc20.experiment.model.Workload
+import org.opendc.harness.dsl.anyOf
+
+/**
+ * A [Portfolio] that explores the effect of operational phenomena on metrics.
+ */
+public class OperationalPhenomenaPortfolio : Portfolio("operational_phenomena") {
+ override val topology: Topology by anyOf(
+ Topology("base")
+ )
+
+ override val workload: Workload by anyOf(
+ Workload("solvinity", 0.1),
+ Workload("solvinity", 0.25),
+ Workload("solvinity", 0.5),
+ Workload("solvinity", 1.0)
+ )
+
+ override val operationalPhenomena: OperationalPhenomena by anyOf(
+ OperationalPhenomena(failureFrequency = 24.0 * 7, hasInterference = true),
+ OperationalPhenomena(failureFrequency = 0.0, hasInterference = true),
+ OperationalPhenomena(failureFrequency = 24.0 * 7, hasInterference = false),
+ OperationalPhenomena(failureFrequency = 0.0, hasInterference = false)
+ )
+
+ override val allocationPolicy: String by anyOf(
+ "mem",
+ "mem-inv",
+ "core-mem",
+ "core-mem-inv",
+ "active-servers",
+ "active-servers-inv",
+ "random"
+ )
+}
diff --git a/simulator/opendc-experiments/opendc-experiments-sc20/src/main/kotlin/org/opendc/experiments/sc20/experiment/Portfolio.kt b/simulator/opendc-experiments/opendc-experiments-sc20/src/main/kotlin/org/opendc/experiments/sc20/experiment/Portfolio.kt
index 37cf2880..4a82ad56 100644
--- a/simulator/opendc-experiments/opendc-experiments-sc20/src/main/kotlin/org/opendc/experiments/sc20/experiment/Portfolio.kt
+++ b/simulator/opendc-experiments/opendc-experiments-sc20/src/main/kotlin/org/opendc/experiments/sc20/experiment/Portfolio.kt
@@ -22,67 +22,196 @@
package org.opendc.experiments.sc20.experiment
+import kotlinx.coroutines.ExperimentalCoroutinesApi
+import kotlinx.coroutines.cancel
+import kotlinx.coroutines.channels.Channel
+import kotlinx.coroutines.launch
+import kotlinx.coroutines.test.TestCoroutineScope
+import mu.KotlinLogging
+import org.opendc.compute.simulator.allocation.*
+import org.opendc.experiments.sc20.experiment.model.CompositeWorkload
import org.opendc.experiments.sc20.experiment.model.OperationalPhenomena
import org.opendc.experiments.sc20.experiment.model.Topology
import org.opendc.experiments.sc20.experiment.model.Workload
-import org.opendc.experiments.sc20.runner.ContainerExperimentDescriptor
+import org.opendc.experiments.sc20.experiment.monitor.ParquetExperimentMonitor
+import org.opendc.experiments.sc20.trace.Sc20ParquetTraceReader
+import org.opendc.experiments.sc20.trace.Sc20RawParquetTraceReader
+import org.opendc.format.environment.sc20.Sc20ClusterEnvironmentReader
+import org.opendc.format.trace.PerformanceInterferenceModelReader
+import org.opendc.harness.dsl.Experiment
+import org.opendc.harness.dsl.anyOf
+import org.opendc.simulator.utils.DelayControllerClockAdapter
+import org.opendc.trace.core.EventTracer
+import java.io.File
+import java.util.concurrent.ConcurrentHashMap
+import kotlin.random.Random
/**
- * A portfolio represents a collection of scenarios are tested.
+ * A portfolio represents a collection of scenarios are tested for the work.
+ *
+ * @param name The name of the portfolio.
*/
-public abstract class Portfolio(
- override val parent: Experiment,
- public val id: Int,
- public val name: String
-) : ContainerExperimentDescriptor() {
+public abstract class Portfolio(name: String) : Experiment(name) {
+ /**
+ * The logger for this portfolio instance.
+ */
+ private val logger = KotlinLogging.logger {}
+
+ /**
+ * The path to where the environments are located.
+ */
+ private val environmentPath by anyOf(File("environments/"))
+
+ /**
+ * The path to where the traces are located.
+ */
+ private val tracePath by anyOf(File("traces/"))
+
/**
- * The topologies to consider.
+ * The path to where the output results should be written.
*/
- protected abstract val topologies: List<Topology>
+ private val outputPath by anyOf(File("results/"))
/**
- * The workloads to consider.
+ * The path to the original VM placements file.
*/
- protected abstract val workloads: List<Workload>
+ private val vmPlacements by anyOf(emptyMap<String, String>())
+
+ /**
+ * The path to the performance interference model.
+ */
+ private val performanceInterferenceModel by anyOf<PerformanceInterferenceModelReader?>(null)
+
+ /**
+ * The topology to test.
+ */
+ public abstract val topology: Topology
+
+ /**
+ * The workload to test.
+ */
+ public abstract val workload: Workload
/**
* The operational phenomenas to consider.
*/
- protected abstract val operationalPhenomenas: List<OperationalPhenomena>
+ public abstract val operationalPhenomena: OperationalPhenomena
/**
* The allocation policies to consider.
*/
- protected abstract val allocationPolicies: List<String>
+ public abstract val allocationPolicy: String
/**
- * The number of repetitions to perform.
+ * A map of trace readers.
*/
- public open val repetitions: Int = 32
+ private val traceReaders = ConcurrentHashMap<String, Sc20RawParquetTraceReader>()
/**
- * Resolve the children of this container.
+ * Perform a single trial for this portfolio.
*/
- override val children: Sequence<Scenario> = sequence {
- var id = 0
- for (topology in topologies) {
- for (workload in workloads) {
- for (operationalPhenomena in operationalPhenomenas) {
- for (allocationPolicy in allocationPolicies) {
- yield(
- Scenario(
- this@Portfolio,
- id++,
- repetitions,
- topology,
- workload,
- allocationPolicy,
- operationalPhenomena
- )
- )
- }
- }
+ @OptIn(ExperimentalCoroutinesApi::class)
+ override fun doRun(repeat: Int) {
+ val testScope = TestCoroutineScope()
+ val clock = DelayControllerClockAdapter(testScope)
+ val tracer = EventTracer(clock)
+ val seeder = Random(repeat)
+ val environment = Sc20ClusterEnvironmentReader(File(environmentPath, "${topology.name}.txt"))
+
+ val chan = Channel<Unit>(Channel.CONFLATED)
+ val allocationPolicy = createAllocationPolicy(seeder)
+
+ val workload = workload
+ val workloadNames = if (workload is CompositeWorkload) {
+ workload.workloads.map { it.name }
+ } else {
+ listOf(workload.name)
+ }
+
+ val rawReaders = workloadNames.map { workloadName ->
+ traceReaders.computeIfAbsent(workloadName) {
+ logger.info { "Loading trace $workloadName" }
+ Sc20RawParquetTraceReader(File(tracePath, workloadName))
+ }
+ }
+
+ val performanceInterferenceModel = performanceInterferenceModel
+ ?.takeIf { operationalPhenomena.hasInterference }
+ ?.construct(seeder) ?: emptyMap()
+ val trace = Sc20ParquetTraceReader(rawReaders, performanceInterferenceModel, workload, seeder.nextInt())
+
+ val monitor = ParquetExperimentMonitor(
+ outputPath,
+ "portfolio_id=$name/scenario_id=$id/run_id=$repeat",
+ 4096
+ )
+
+ testScope.launch {
+ val (bareMetalProvisioner, scheduler) = createProvisioner(
+ this,
+ clock,
+ environment,
+ allocationPolicy,
+ tracer
+ )
+
+ val failureDomain = if (operationalPhenomena.failureFrequency > 0) {
+ logger.debug("ENABLING failures")
+ createFailureDomain(
+ this,
+ clock,
+ seeder.nextInt(),
+ operationalPhenomena.failureFrequency,
+ bareMetalProvisioner,
+ chan
+ )
+ } else {
+ null
}
+
+ attachMonitor(this, clock, scheduler, monitor)
+ processTrace(
+ this,
+ clock,
+ trace,
+ scheduler,
+ chan,
+ monitor
+ )
+
+ logger.debug("SUBMIT=${scheduler.submittedVms}")
+ logger.debug("FAIL=${scheduler.unscheduledVms}")
+ logger.debug("QUEUED=${scheduler.queuedVms}")
+ logger.debug("RUNNING=${scheduler.runningVms}")
+ logger.debug("FINISHED=${scheduler.finishedVms}")
+
+ failureDomain?.cancel()
+ scheduler.terminate()
+ }
+
+ try {
+ testScope.advanceUntilIdle()
+ } finally {
+ monitor.close()
+ }
+ }
+
+ /**
+ * Create the [AllocationPolicy] instance to use for the trial.
+ */
+ private fun createAllocationPolicy(seeder: Random): AllocationPolicy {
+ return when (allocationPolicy) {
+ "mem" -> AvailableMemoryAllocationPolicy()
+ "mem-inv" -> AvailableMemoryAllocationPolicy(true)
+ "core-mem" -> AvailableCoreMemoryAllocationPolicy()
+ "core-mem-inv" -> AvailableCoreMemoryAllocationPolicy(true)
+ "active-servers" -> NumberOfActiveServersAllocationPolicy()
+ "active-servers-inv" -> NumberOfActiveServersAllocationPolicy(true)
+ "provisioned-cores" -> ProvisionedCoresAllocationPolicy()
+ "provisioned-cores-inv" -> ProvisionedCoresAllocationPolicy(true)
+ "random" -> RandomAllocationPolicy(Random(seeder.nextInt()))
+ "replay" -> ReplayAllocationPolicy(vmPlacements)
+ else -> throw IllegalArgumentException("Unknown policy $allocationPolicy")
}
}
}
diff --git a/simulator/opendc-experiments/opendc-experiments-sc20/src/main/kotlin/org/opendc/experiments/sc20/experiment/Portfolios.kt b/simulator/opendc-experiments/opendc-experiments-sc20/src/main/kotlin/org/opendc/experiments/sc20/experiment/Portfolios.kt
deleted file mode 100644
index 249a63b9..00000000
--- a/simulator/opendc-experiments/opendc-experiments-sc20/src/main/kotlin/org/opendc/experiments/sc20/experiment/Portfolios.kt
+++ /dev/null
@@ -1,221 +0,0 @@
-/*
- * Copyright (c) 2020 AtLarge Research
- *
- * Permission is hereby granted, free of charge, to any person obtaining a copy
- * of this software and associated documentation files (the "Software"), to deal
- * in the Software without restriction, including without limitation the rights
- * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
- * copies of the Software, and to permit persons to whom the Software is
- * furnished to do so, subject to the following conditions:
- *
- * The above copyright notice and this permission notice shall be included in all
- * copies or substantial portions of the Software.
- *
- * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
- * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
- * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
- * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
- * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
- * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
- * SOFTWARE.
- */
-
-package org.opendc.experiments.sc20.experiment
-
-import org.opendc.experiments.sc20.experiment.model.*
-
-public class HorVerPortfolio(parent: Experiment, id: Int) : Portfolio(parent, id, "horizontal_vs_vertical") {
- override val topologies: List<Topology> = listOf(
- Topology("base"),
- Topology("rep-vol-hor-hom"),
- Topology("rep-vol-hor-het"),
- Topology("rep-vol-ver-hom"),
- Topology("rep-vol-ver-het"),
- Topology("exp-vol-hor-hom"),
- Topology("exp-vol-hor-het"),
- Topology("exp-vol-ver-hom"),
- Topology("exp-vol-ver-het")
- )
-
- override val workloads: List<Workload> = listOf(
- Workload("solvinity", 0.1),
- Workload("solvinity", 0.25),
- Workload("solvinity", 0.5),
- Workload("solvinity", 1.0)
- )
-
- override val operationalPhenomenas: List<OperationalPhenomena> = listOf(
- OperationalPhenomena(failureFrequency = 24.0 * 7, hasInterference = true)
- )
-
- override val allocationPolicies: List<String> = listOf(
- "active-servers"
- )
-}
-
-public class MoreVelocityPortfolio(parent: Experiment, id: Int) : Portfolio(parent, id, "more_velocity") {
- override val topologies: List<Topology> = listOf(
- Topology("base"),
- Topology("rep-vel-ver-hom"),
- Topology("rep-vel-ver-het"),
- Topology("exp-vel-ver-hom"),
- Topology("exp-vel-ver-het")
- )
-
- override val workloads: List<Workload> = listOf(
- Workload("solvinity", 0.1),
- Workload("solvinity", 0.25),
- Workload("solvinity", 0.5),
- Workload("solvinity", 1.0)
- )
-
- override val operationalPhenomenas: List<OperationalPhenomena> = listOf(
- OperationalPhenomena(failureFrequency = 24.0 * 7, hasInterference = true)
- )
-
- override val allocationPolicies: List<String> = listOf(
- "active-servers"
- )
-}
-
-public class CompositeWorkloadPortfolio(parent: Experiment, id: Int) : Portfolio(parent, id, "composite-workload") {
- private val totalSampleLoad = 1.3301733005049648E12
-
- override val topologies: List<Topology> = listOf(
- Topology("base"),
- Topology("exp-vol-hor-hom"),
- Topology("exp-vol-ver-hom"),
- Topology("exp-vel-ver-hom")
- )
-
- override val workloads: List<Workload> = listOf(
- CompositeWorkload(
- "all-azure",
- listOf(Workload("solvinity-short", 0.0), Workload("azure", 1.0)),
- totalSampleLoad
- ),
- CompositeWorkload(
- "solvinity-25-azure-75",
- listOf(Workload("solvinity-short", 0.25), Workload("azure", 0.75)),
- totalSampleLoad
- ),
- CompositeWorkload(
- "solvinity-50-azure-50",
- listOf(Workload("solvinity-short", 0.5), Workload("azure", 0.5)),
- totalSampleLoad
- ),
- CompositeWorkload(
- "solvinity-75-azure-25",
- listOf(Workload("solvinity-short", 0.75), Workload("azure", 0.25)),
- totalSampleLoad
- ),
- CompositeWorkload(
- "all-solvinity",
- listOf(Workload("solvinity-short", 1.0), Workload("azure", 0.0)),
- totalSampleLoad
- )
- )
-
- override val operationalPhenomenas: List<OperationalPhenomena> = listOf(
- OperationalPhenomena(failureFrequency = 24.0 * 7, hasInterference = false)
- )
-
- override val allocationPolicies: List<String> = listOf(
- "active-servers"
- )
-}
-
-public class OperationalPhenomenaPortfolio(parent: Experiment, id: Int) :
- Portfolio(parent, id, "operational_phenomena") {
- override val topologies: List<Topology> = listOf(
- Topology("base")
- )
-
- override val workloads: List<Workload> = listOf(
- Workload("solvinity", 0.1),
- Workload("solvinity", 0.25),
- Workload("solvinity", 0.5),
- Workload("solvinity", 1.0)
- )
-
- override val operationalPhenomenas: List<OperationalPhenomena> = listOf(
- OperationalPhenomena(failureFrequency = 24.0 * 7, hasInterference = true),
- OperationalPhenomena(failureFrequency = 0.0, hasInterference = true),
- OperationalPhenomena(failureFrequency = 24.0 * 7, hasInterference = false),
- OperationalPhenomena(failureFrequency = 0.0, hasInterference = false)
- )
-
- override val allocationPolicies: List<String> = listOf(
- "mem",
- "mem-inv",
- "core-mem",
- "core-mem-inv",
- "active-servers",
- "active-servers-inv",
- "random"
- )
-}
-
-public class ReplayPortfolio(parent: Experiment, id: Int) : Portfolio(parent, id, "replay") {
- override val topologies: List<Topology> = listOf(
- Topology("base")
- )
-
- override val workloads: List<Workload> = listOf(
- Workload("solvinity", 1.0)
- )
-
- override val operationalPhenomenas: List<OperationalPhenomena> = listOf(
- OperationalPhenomena(failureFrequency = 0.0, hasInterference = false)
- )
-
- override val allocationPolicies: List<String> = listOf(
- "replay",
- "active-servers"
- )
-}
-
-public class TestPortfolio(parent: Experiment, id: Int) : Portfolio(parent, id, "test") {
- override val repetitions: Int = 1
-
- override val topologies: List<Topology> = listOf(
- Topology("base")
- )
-
- override val workloads: List<Workload> = listOf(
- Workload("solvinity", 1.0)
- )
-
- override val operationalPhenomenas: List<OperationalPhenomena> = listOf(
- OperationalPhenomena(failureFrequency = 24.0 * 7, hasInterference = true)
- )
-
- override val allocationPolicies: List<String> = listOf("active-servers")
-}
-
-public class MoreHpcPortfolio(parent: Experiment, id: Int) : Portfolio(parent, id, "more_hpc") {
- override val topologies: List<Topology> = listOf(
- Topology("base"),
- Topology("exp-vol-hor-hom"),
- Topology("exp-vol-ver-hom"),
- Topology("exp-vel-ver-hom")
- )
-
- override val workloads: List<Workload> = listOf(
- Workload("solvinity", 0.0, samplingStrategy = SamplingStrategy.HPC),
- Workload("solvinity", 0.25, samplingStrategy = SamplingStrategy.HPC),
- Workload("solvinity", 0.5, samplingStrategy = SamplingStrategy.HPC),
- Workload("solvinity", 1.0, samplingStrategy = SamplingStrategy.HPC),
- Workload("solvinity", 0.25, samplingStrategy = SamplingStrategy.HPC_LOAD),
- Workload("solvinity", 0.5, samplingStrategy = SamplingStrategy.HPC_LOAD),
- Workload("solvinity", 1.0, samplingStrategy = SamplingStrategy.HPC_LOAD)
- )
-
- override val operationalPhenomenas: List<OperationalPhenomena> = listOf(
- OperationalPhenomena(failureFrequency = 24.0 * 7, hasInterference = true)
- )
-
- override val allocationPolicies: List<String> = listOf(
- "active-servers"
- )
-}
diff --git a/simulator/opendc-experiments/opendc-experiments-sc20/src/main/kotlin/org/opendc/experiments/sc20/experiment/Scenario.kt b/simulator/opendc-experiments/opendc-experiments-sc20/src/main/kotlin/org/opendc/experiments/sc20/experiment/ReplayPortfolio.kt
index d092ddd5..8a42a3b4 100644
--- a/simulator/opendc-experiments/opendc-experiments-sc20/src/main/kotlin/org/opendc/experiments/sc20/experiment/Scenario.kt
+++ b/simulator/opendc-experiments/opendc-experiments-sc20/src/main/kotlin/org/opendc/experiments/sc20/experiment/ReplayPortfolio.kt
@@ -1,5 +1,5 @@
/*
- * Copyright (c) 2020 AtLarge Research
+ * Copyright (c) 2021 AtLarge Research
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to deal
@@ -25,22 +25,26 @@ package org.opendc.experiments.sc20.experiment
import org.opendc.experiments.sc20.experiment.model.OperationalPhenomena
import org.opendc.experiments.sc20.experiment.model.Topology
import org.opendc.experiments.sc20.experiment.model.Workload
-import org.opendc.experiments.sc20.runner.ContainerExperimentDescriptor
-import org.opendc.experiments.sc20.runner.ExperimentDescriptor
+import org.opendc.harness.dsl.anyOf
/**
- * A scenario represents a single point in the design space (a unique combination of parameters).
+ * A [Portfolio] that compares the original VM placements against our policies.
*/
-public class Scenario(
- override val parent: Portfolio,
- public val id: Int,
- public val repetitions: Int,
- public val topology: Topology,
- public val workload: Workload,
- public val allocationPolicy: String,
- public val operationalPhenomena: OperationalPhenomena
-) : ContainerExperimentDescriptor() {
- override val children: Sequence<ExperimentDescriptor> = sequence {
- repeat(repetitions) { i -> yield(Run(this@Scenario, i, i)) }
- }
+public class ReplayPortfolio : Portfolio("replay") {
+ override val topology: Topology by anyOf(
+ Topology("base")
+ )
+
+ override val workload: Workload by anyOf(
+ Workload("solvinity", 1.0)
+ )
+
+ override val operationalPhenomena: OperationalPhenomena by anyOf(
+ OperationalPhenomena(failureFrequency = 0.0, hasInterference = false)
+ )
+
+ override val allocationPolicy: String by anyOf(
+ "replay",
+ "active-servers"
+ )
}
diff --git a/simulator/opendc-experiments/opendc-experiments-sc20/src/main/kotlin/org/opendc/experiments/sc20/experiment/Run.kt b/simulator/opendc-experiments/opendc-experiments-sc20/src/main/kotlin/org/opendc/experiments/sc20/experiment/Run.kt
deleted file mode 100644
index 660fc882..00000000
--- a/simulator/opendc-experiments/opendc-experiments-sc20/src/main/kotlin/org/opendc/experiments/sc20/experiment/Run.kt
+++ /dev/null
@@ -1,153 +0,0 @@
-/*
- * Copyright (c) 2020 AtLarge Research
- *
- * Permission is hereby granted, free of charge, to any person obtaining a copy
- * of this software and associated documentation files (the "Software"), to deal
- * in the Software without restriction, including without limitation the rights
- * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
- * copies of the Software, and to permit persons to whom the Software is
- * furnished to do so, subject to the following conditions:
- *
- * The above copyright notice and this permission notice shall be included in all
- * copies or substantial portions of the Software.
- *
- * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
- * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
- * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
- * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
- * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
- * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
- * SOFTWARE.
- */
-
-package org.opendc.experiments.sc20.experiment
-
-import kotlinx.coroutines.ExperimentalCoroutinesApi
-import kotlinx.coroutines.cancel
-import kotlinx.coroutines.channels.Channel
-import kotlinx.coroutines.launch
-import kotlinx.coroutines.test.TestCoroutineScope
-import mu.KotlinLogging
-import org.opendc.compute.simulator.allocation.*
-import org.opendc.experiments.sc20.experiment.model.CompositeWorkload
-import org.opendc.experiments.sc20.experiment.monitor.ParquetExperimentMonitor
-import org.opendc.experiments.sc20.runner.TrialExperimentDescriptor
-import org.opendc.experiments.sc20.runner.execution.ExperimentExecutionContext
-import org.opendc.experiments.sc20.trace.Sc20ParquetTraceReader
-import org.opendc.experiments.sc20.trace.Sc20RawParquetTraceReader
-import org.opendc.format.environment.sc20.Sc20ClusterEnvironmentReader
-import org.opendc.simulator.utils.DelayControllerClockAdapter
-import java.io.File
-import kotlin.random.Random
-
-/**
- * The logger for the experiment scenario.
- */
-private val logger = KotlinLogging.logger {}
-
-/**
- * An experiment run represent a single invocation of a trial and is used to distinguish between repetitions of the
- * same set of parameters.
- */
-@OptIn(ExperimentalCoroutinesApi::class)
-public data class Run(override val parent: Scenario, val id: Int, val seed: Int) : TrialExperimentDescriptor() {
- override suspend fun invoke(context: ExperimentExecutionContext) {
- val experiment = parent.parent.parent
- val testScope = TestCoroutineScope()
- val clock = DelayControllerClockAdapter(testScope)
- val seeder = Random(seed)
- val environment = Sc20ClusterEnvironmentReader(File(experiment.environments, "${parent.topology.name}.txt"))
-
- val chan = Channel<Unit>(Channel.CONFLATED)
- val allocationPolicy = when (parent.allocationPolicy) {
- "mem" -> AvailableMemoryAllocationPolicy()
- "mem-inv" -> AvailableMemoryAllocationPolicy(true)
- "core-mem" -> AvailableCoreMemoryAllocationPolicy()
- "core-mem-inv" -> AvailableCoreMemoryAllocationPolicy(true)
- "active-servers" -> NumberOfActiveServersAllocationPolicy()
- "active-servers-inv" -> NumberOfActiveServersAllocationPolicy(true)
- "provisioned-cores" -> ProvisionedCoresAllocationPolicy()
- "provisioned-cores-inv" -> ProvisionedCoresAllocationPolicy(true)
- "random" -> RandomAllocationPolicy(Random(seeder.nextInt()))
- "replay" -> ReplayAllocationPolicy(experiment.vmPlacements)
- else -> throw IllegalArgumentException("Unknown policy ${parent.allocationPolicy}")
- }
-
- @Suppress("UNCHECKED_CAST")
- val rawTraceReaders =
- context.cache.computeIfAbsent("raw-trace-readers") { mutableMapOf<String, Sc20RawParquetTraceReader>() } as MutableMap<String, Sc20RawParquetTraceReader>
- val rawReaders = synchronized(rawTraceReaders) {
- val workloadNames = if (parent.workload is CompositeWorkload) {
- parent.workload.workloads.map { it.name }
- } else {
- listOf(parent.workload.name)
- }
-
- workloadNames.map { workloadName ->
- rawTraceReaders.computeIfAbsent(workloadName) {
- logger.info { "Loading trace $workloadName" }
- Sc20RawParquetTraceReader(File(experiment.traces, workloadName))
- }
- }
- }
-
- val performanceInterferenceModel = experiment.performanceInterferenceModel
- ?.takeIf { parent.operationalPhenomena.hasInterference }
- ?.construct(seeder) ?: emptyMap()
- val trace = Sc20ParquetTraceReader(rawReaders, performanceInterferenceModel, parent.workload, seed)
-
- val monitor = ParquetExperimentMonitor(
- parent.parent.parent.output,
- "portfolio_id=${parent.parent.id}/scenario_id=${parent.id}/run_id=$id",
- parent.parent.parent.bufferSize
- )
-
- testScope.launch {
- val (bareMetalProvisioner, scheduler) = createProvisioner(
- this,
- clock,
- environment,
- allocationPolicy
- )
-
- val failureDomain = if (parent.operationalPhenomena.failureFrequency > 0) {
- logger.debug("ENABLING failures")
- createFailureDomain(
- this,
- clock,
- seeder.nextInt(),
- parent.operationalPhenomena.failureFrequency,
- bareMetalProvisioner,
- chan
- )
- } else {
- null
- }
-
- attachMonitor(this, clock, scheduler, monitor)
- processTrace(
- this,
- clock,
- trace,
- scheduler,
- chan,
- monitor
- )
-
- logger.debug("SUBMIT=${scheduler.submittedVms}")
- logger.debug("FAIL=${scheduler.unscheduledVms}")
- logger.debug("QUEUED=${scheduler.queuedVms}")
- logger.debug("RUNNING=${scheduler.runningVms}")
- logger.debug("FINISHED=${scheduler.finishedVms}")
-
- failureDomain?.cancel()
- scheduler.terminate()
- }
-
- try {
- testScope.advanceUntilIdle()
- } finally {
- monitor.close()
- }
- }
-}
diff --git a/simulator/opendc-experiments/opendc-experiments-sc20/src/main/kotlin/org/opendc/experiments/sc20/experiment/TestPortfolio.kt b/simulator/opendc-experiments/opendc-experiments-sc20/src/main/kotlin/org/opendc/experiments/sc20/experiment/TestPortfolio.kt
new file mode 100644
index 00000000..2210fc97
--- /dev/null
+++ b/simulator/opendc-experiments/opendc-experiments-sc20/src/main/kotlin/org/opendc/experiments/sc20/experiment/TestPortfolio.kt
@@ -0,0 +1,47 @@
+/*
+ * 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.experiments.sc20.experiment
+
+import org.opendc.experiments.sc20.experiment.model.OperationalPhenomena
+import org.opendc.experiments.sc20.experiment.model.Topology
+import org.opendc.experiments.sc20.experiment.model.Workload
+import org.opendc.harness.dsl.anyOf
+
+/**
+ * A [Portfolio] to perform a simple test run.
+ */
+public class TestPortfolio : Portfolio("test") {
+ override val topology: Topology by anyOf(
+ Topology("base")
+ )
+
+ override val workload: Workload by anyOf(
+ Workload("solvinity", 1.0)
+ )
+
+ override val operationalPhenomena: OperationalPhenomena by anyOf(
+ OperationalPhenomena(failureFrequency = 24.0 * 7, hasInterference = true)
+ )
+
+ override val allocationPolicy: String by anyOf("active-servers")
+}
diff --git a/simulator/opendc-experiments/opendc-experiments-sc20/src/main/kotlin/org/opendc/experiments/sc20/runner/ContainerExperimentDescriptor.kt b/simulator/opendc-experiments/opendc-experiments-sc20/src/main/kotlin/org/opendc/experiments/sc20/runner/ContainerExperimentDescriptor.kt
deleted file mode 100644
index d70e8c9a..00000000
--- a/simulator/opendc-experiments/opendc-experiments-sc20/src/main/kotlin/org/opendc/experiments/sc20/runner/ContainerExperimentDescriptor.kt
+++ /dev/null
@@ -1,66 +0,0 @@
-/*
- * Copyright (c) 2020 AtLarge Research
- *
- * Permission is hereby granted, free of charge, to any person obtaining a copy
- * of this software and associated documentation files (the "Software"), to deal
- * in the Software without restriction, including without limitation the rights
- * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
- * copies of the Software, and to permit persons to whom the Software is
- * furnished to do so, subject to the following conditions:
- *
- * The above copyright notice and this permission notice shall be included in all
- * copies or substantial portions of the Software.
- *
- * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
- * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
- * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
- * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
- * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
- * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
- * SOFTWARE.
- */
-
-package org.opendc.experiments.sc20.runner
-
-import kotlinx.coroutines.launch
-import kotlinx.coroutines.supervisorScope
-import org.opendc.experiments.sc20.runner.execution.ExperimentExecutionContext
-import org.opendc.experiments.sc20.runner.execution.ExperimentExecutionResult
-
-/**
- * An abstract [ExperimentDescriptor] specifically for containers.
- */
-public abstract class ContainerExperimentDescriptor : ExperimentDescriptor() {
- /**
- * The child descriptors of this container.
- */
- public abstract val children: Sequence<ExperimentDescriptor>
-
- override val type: Type = Type.CONTAINER
-
- override suspend fun invoke(context: ExperimentExecutionContext) {
- val materializedChildren = children.toList()
- for (child in materializedChildren) {
- context.listener.descriptorRegistered(child)
- }
-
- supervisorScope {
- for (child in materializedChildren) {
- if (child.isTrial) {
- launch {
- val worker = context.scheduler.allocate()
- context.listener.executionStarted(child)
- try {
- worker(child, context)
- context.listener.executionFinished(child, ExperimentExecutionResult.Success)
- } catch (e: Throwable) {
- context.listener.executionFinished(child, ExperimentExecutionResult.Failed(e))
- }
- }
- } else {
- launch { child(context) }
- }
- }
- }
- }
-}
diff --git a/simulator/opendc-experiments/opendc-experiments-sc20/src/main/kotlin/org/opendc/experiments/sc20/runner/ExperimentDescriptor.kt b/simulator/opendc-experiments/opendc-experiments-sc20/src/main/kotlin/org/opendc/experiments/sc20/runner/ExperimentDescriptor.kt
deleted file mode 100644
index 1e67c086..00000000
--- a/simulator/opendc-experiments/opendc-experiments-sc20/src/main/kotlin/org/opendc/experiments/sc20/runner/ExperimentDescriptor.kt
+++ /dev/null
@@ -1,79 +0,0 @@
-/*
- * Copyright (c) 2020 AtLarge Research
- *
- * Permission is hereby granted, free of charge, to any person obtaining a copy
- * of this software and associated documentation files (the "Software"), to deal
- * in the Software without restriction, including without limitation the rights
- * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
- * copies of the Software, and to permit persons to whom the Software is
- * furnished to do so, subject to the following conditions:
- *
- * The above copyright notice and this permission notice shall be included in all
- * copies or substantial portions of the Software.
- *
- * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
- * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
- * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
- * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
- * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
- * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
- * SOFTWARE.
- */
-
-package org.opendc.experiments.sc20.runner
-
-import org.opendc.experiments.sc20.runner.execution.ExperimentExecutionContext
-import java.io.Serializable
-
-/**
- * An immutable description of an experiment in the **odcsim* simulation framework, which may be a single atomic trial
- * or a composition of multiple trials.
- *
- * This class represents a dynamic tree-like structure where the children of the nodes are not known at instantiation
- * since they might be generated dynamically.
- */
-public abstract class ExperimentDescriptor : Serializable {
- /**
- * The parent of this descriptor, or `null` if it has no parent.
- */
- public abstract val parent: ExperimentDescriptor?
-
- /**
- * The type of descriptor.
- */
- public abstract val type: Type
-
- /**
- * A flag to indicate that this descriptor is a root descriptor.
- */
- public open val isRoot: Boolean
- get() = parent == null
-
- /**
- * A flag to indicate that this descriptor describes an experiment trial.
- */
- public val isTrial: Boolean
- get() = type == Type.TRIAL
-
- /**
- * Execute this [ExperimentDescriptor].
- *
- * @param context The context to execute the descriptor in.
- */
- public abstract suspend operator fun invoke(context: ExperimentExecutionContext)
-
- /**
- * The types of experiment descriptors.
- */
- public enum class Type {
- /**
- * A composition of multiple experiment descriptions whose invocation happens on a single thread.
- */
- CONTAINER,
-
- /**
- * An invocation of a single scenario of an experiment whose invocation may happen on different threads.
- */
- TRIAL
- }
-}
diff --git a/simulator/opendc-experiments/opendc-experiments-sc20/src/main/kotlin/org/opendc/experiments/sc20/runner/execution/ThreadPoolExperimentScheduler.kt b/simulator/opendc-experiments/opendc-experiments-sc20/src/main/kotlin/org/opendc/experiments/sc20/runner/execution/ThreadPoolExperimentScheduler.kt
deleted file mode 100644
index 942faed1..00000000
--- a/simulator/opendc-experiments/opendc-experiments-sc20/src/main/kotlin/org/opendc/experiments/sc20/runner/execution/ThreadPoolExperimentScheduler.kt
+++ /dev/null
@@ -1,83 +0,0 @@
-/*
- * Copyright (c) 2020 AtLarge Research
- *
- * Permission is hereby granted, free of charge, to any person obtaining a copy
- * of this software and associated documentation files (the "Software"), to deal
- * in the Software without restriction, including without limitation the rights
- * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
- * copies of the Software, and to permit persons to whom the Software is
- * furnished to do so, subject to the following conditions:
- *
- * The above copyright notice and this permission notice shall be included in all
- * copies or substantial portions of the Software.
- *
- * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
- * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
- * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
- * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
- * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
- * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
- * SOFTWARE.
- */
-
-package org.opendc.experiments.sc20.runner.execution
-
-import kotlinx.coroutines.asCoroutineDispatcher
-import kotlinx.coroutines.launch
-import kotlinx.coroutines.supervisorScope
-import kotlinx.coroutines.sync.Semaphore
-import kotlinx.coroutines.withContext
-import org.opendc.experiments.sc20.runner.ExperimentDescriptor
-import java.util.concurrent.Executors
-
-/**
- * An [ExperimentScheduler] that runs experiments using a local thread pool.
- *
- * @param parallelism The maximum amount of parallel workers (default is the number of available processors).
- */
-public class ThreadPoolExperimentScheduler(parallelism: Int = Runtime.getRuntime().availableProcessors() + 1) : ExperimentScheduler {
- private val dispatcher = Executors.newCachedThreadPool().asCoroutineDispatcher()
- private val tickets = Semaphore(parallelism)
-
- override suspend fun allocate(): ExperimentScheduler.Worker {
- tickets.acquire()
- return object : ExperimentScheduler.Worker {
- override suspend fun invoke(
- descriptor: ExperimentDescriptor,
- context: ExperimentExecutionContext
- ) = supervisorScope {
- val listener =
- object : ExperimentExecutionListener {
- override fun descriptorRegistered(descriptor: ExperimentDescriptor) {
- launch { context.listener.descriptorRegistered(descriptor) }
- }
-
- override fun executionFinished(
- descriptor: ExperimentDescriptor,
- result: ExperimentExecutionResult
- ) {
- launch { context.listener.executionFinished(descriptor, result) }
- }
-
- override fun executionStarted(descriptor: ExperimentDescriptor) {
- launch { context.listener.executionStarted(descriptor) }
- }
- }
-
- val newContext = object : ExperimentExecutionContext by context {
- override val listener: ExperimentExecutionListener = listener
- }
-
- try {
- withContext(dispatcher) {
- descriptor(newContext)
- }
- } finally {
- tickets.release()
- }
- }
- }
- }
-
- override fun close(): Unit = dispatcher.close()
-}
diff --git a/simulator/opendc-experiments/opendc-experiments-sc20/src/main/kotlin/org/opendc/experiments/sc20/runner/internal/DefaultExperimentRunner.kt b/simulator/opendc-experiments/opendc-experiments-sc20/src/main/kotlin/org/opendc/experiments/sc20/runner/internal/DefaultExperimentRunner.kt
deleted file mode 100644
index 26e4df89..00000000
--- a/simulator/opendc-experiments/opendc-experiments-sc20/src/main/kotlin/org/opendc/experiments/sc20/runner/internal/DefaultExperimentRunner.kt
+++ /dev/null
@@ -1,60 +0,0 @@
-/*
- * Copyright (c) 2020 AtLarge Research
- *
- * Permission is hereby granted, free of charge, to any person obtaining a copy
- * of this software and associated documentation files (the "Software"), to deal
- * in the Software without restriction, including without limitation the rights
- * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
- * copies of the Software, and to permit persons to whom the Software is
- * furnished to do so, subject to the following conditions:
- *
- * The above copyright notice and this permission notice shall be included in all
- * copies or substantial portions of the Software.
- *
- * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
- * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
- * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
- * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
- * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
- * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
- * SOFTWARE.
- */
-
-package org.opendc.experiments.sc20.runner.internal
-
-import kotlinx.coroutines.runBlocking
-import org.opendc.experiments.sc20.runner.ExperimentDescriptor
-import org.opendc.experiments.sc20.runner.ExperimentRunner
-import org.opendc.experiments.sc20.runner.execution.ExperimentExecutionContext
-import org.opendc.experiments.sc20.runner.execution.ExperimentExecutionListener
-import org.opendc.experiments.sc20.runner.execution.ExperimentExecutionResult
-import org.opendc.experiments.sc20.runner.execution.ExperimentScheduler
-import java.util.concurrent.ConcurrentHashMap
-
-/**
- * The default implementation of the [ExperimentRunner] interface.
- *
- * @param scheduler The scheduler to use.
- */
-public class DefaultExperimentRunner(private val scheduler: ExperimentScheduler) : ExperimentRunner {
- override val id: String = "default"
-
- override val version: String? = "1.0"
-
- override fun execute(root: ExperimentDescriptor, listener: ExperimentExecutionListener): Unit = runBlocking {
- val context = object : ExperimentExecutionContext {
- override val listener: ExperimentExecutionListener = listener
- override val scheduler: ExperimentScheduler = this@DefaultExperimentRunner.scheduler
- override val cache: MutableMap<Any?, Any?> = ConcurrentHashMap()
- }
-
- listener.descriptorRegistered(root)
- context.listener.executionStarted(root)
- try {
- root(context)
- context.listener.executionFinished(root, ExperimentExecutionResult.Success)
- } catch (e: Throwable) {
- context.listener.executionFinished(root, ExperimentExecutionResult.Failed(e))
- }
- }
-}
diff --git a/simulator/opendc-experiments/opendc-experiments-sc20/src/main/kotlin/org/opendc/experiments/sc20/telemetry/RunEvent.kt b/simulator/opendc-experiments/opendc-experiments-sc20/src/main/kotlin/org/opendc/experiments/sc20/telemetry/RunEvent.kt
index 3bcd10a1..4f4706f0 100644
--- a/simulator/opendc-experiments/opendc-experiments-sc20/src/main/kotlin/org/opendc/experiments/sc20/telemetry/RunEvent.kt
+++ b/simulator/opendc-experiments/opendc-experiments-sc20/src/main/kotlin/org/opendc/experiments/sc20/telemetry/RunEvent.kt
@@ -22,12 +22,13 @@
package org.opendc.experiments.sc20.telemetry
-import org.opendc.experiments.sc20.experiment.Run
+import org.opendc.experiments.sc20.experiment.Portfolio
/**
* A periodic report of the host machine metrics.
*/
public data class RunEvent(
- public val run: Run,
+ val portfolio: Portfolio,
+ val repeat: Int,
override val timestamp: Long
) : Event("run")
diff --git a/simulator/opendc-experiments/opendc-experiments-sc20/src/main/kotlin/org/opendc/experiments/sc20/telemetry/parquet/ParquetRunEventWriter.kt b/simulator/opendc-experiments/opendc-experiments-sc20/src/main/kotlin/org/opendc/experiments/sc20/telemetry/parquet/ParquetRunEventWriter.kt
index 74efb660..b50a698c 100644
--- a/simulator/opendc-experiments/opendc-experiments-sc20/src/main/kotlin/org/opendc/experiments/sc20/telemetry/parquet/ParquetRunEventWriter.kt
+++ b/simulator/opendc-experiments/opendc-experiments-sc20/src/main/kotlin/org/opendc/experiments/sc20/telemetry/parquet/ParquetRunEventWriter.kt
@@ -38,33 +38,27 @@ public class ParquetRunEventWriter(path: File, bufferSize: Int) :
public companion object {
private val convert: (RunEvent, GenericData.Record) -> Unit = { event, record ->
- val run = event.run
- val scenario = run.parent
- val portfolio = scenario.parent
- record.put("portfolio_id", portfolio.id)
+ val portfolio = event.portfolio
record.put("portfolio_name", portfolio.name)
- record.put("scenario_id", scenario.id)
- record.put("run_id", run.id)
- record.put("repetitions", scenario.repetitions)
- record.put("topology", scenario.topology.name)
- record.put("workload_name", scenario.workload.name)
- record.put("workload_fraction", scenario.workload.fraction)
- record.put("workload_sampler", scenario.workload.samplingStrategy)
- record.put("allocation_policy", scenario.allocationPolicy)
- record.put("failure_frequency", scenario.operationalPhenomena.failureFrequency)
- record.put("interference", scenario.operationalPhenomena.hasInterference)
- record.put("seed", run.seed)
+ record.put("scenario_id", portfolio.id)
+ record.put("run_id", event.repeat)
+ record.put("topology", portfolio.topology.name)
+ record.put("workload_name", portfolio.workload.name)
+ record.put("workload_fraction", portfolio.workload.fraction)
+ record.put("workload_sampler", portfolio.workload.samplingStrategy)
+ record.put("allocation_policy", portfolio.allocationPolicy)
+ record.put("failure_frequency", portfolio.operationalPhenomena.failureFrequency)
+ record.put("interference", portfolio.operationalPhenomena.hasInterference)
+ record.put("seed", event.repeat)
}
private val schema: Schema = SchemaBuilder
.record("runs")
.namespace("org.opendc.experiments.sc20")
.fields()
- .name("portfolio_id").type().intType().noDefault()
.name("portfolio_name").type().stringType().noDefault()
.name("scenario_id").type().intType().noDefault()
.name("run_id").type().intType().noDefault()
- .name("repetitions").type().intType().noDefault()
.name("topology").type().stringType().noDefault()
.name("workload_name").type().stringType().noDefault()
.name("workload_fraction").type().doubleType().noDefault()
diff --git a/simulator/opendc-experiments/opendc-experiments-sc20/src/main/kotlin/org/opendc/experiments/sc20/trace/Sc20RawParquetTraceReader.kt b/simulator/opendc-experiments/opendc-experiments-sc20/src/main/kotlin/org/opendc/experiments/sc20/trace/Sc20RawParquetTraceReader.kt
index 9bc1a58e..4a318df4 100644
--- a/simulator/opendc-experiments/opendc-experiments-sc20/src/main/kotlin/org/opendc/experiments/sc20/trace/Sc20RawParquetTraceReader.kt
+++ b/simulator/opendc-experiments/opendc-experiments-sc20/src/main/kotlin/org/opendc/experiments/sc20/trace/Sc20RawParquetTraceReader.kt
@@ -66,8 +66,6 @@ public class Sc20RawParquetTraceReader(private val path: File) {
val flops = record["flops"] as Long
val fragment = SimTraceWorkload.Fragment(
- tick,
- flops,
duration,
cpuUsage,
cores
diff --git a/simulator/opendc-experiments/opendc-experiments-sc20/src/main/kotlin/org/opendc/experiments/sc20/trace/Sc20StreamingParquetTraceReader.kt b/simulator/opendc-experiments/opendc-experiments-sc20/src/main/kotlin/org/opendc/experiments/sc20/trace/Sc20StreamingParquetTraceReader.kt
index edef276c..ba22ae15 100644
--- a/simulator/opendc-experiments/opendc-experiments-sc20/src/main/kotlin/org/opendc/experiments/sc20/trace/Sc20StreamingParquetTraceReader.kt
+++ b/simulator/opendc-experiments/opendc-experiments-sc20/src/main/kotlin/org/opendc/experiments/sc20/trace/Sc20StreamingParquetTraceReader.kt
@@ -92,7 +92,7 @@ public class Sc20StreamingParquetTraceReader(
/**
* A poisonous fragment.
*/
- private val poison = Pair("\u0000", SimTraceWorkload.Fragment(0, 0, 0, 0.0, 0))
+ private val poison = Pair("\u0000", SimTraceWorkload.Fragment(0, 0.0, 0))
/**
* The thread to read the records in.
@@ -120,8 +120,6 @@ public class Sc20StreamingParquetTraceReader(
val flops = record["flops"] as Long
val fragment = SimTraceWorkload.Fragment(
- tick,
- flops,
duration,
cpuUsage,
cores
@@ -204,6 +202,7 @@ public class Sc20StreamingParquetTraceReader(
val externalBuffer = mutableListOf<SimTraceWorkload.Fragment>()
buffers.getOrPut(id) { mutableListOf() }.add(externalBuffer)
val fragments = sequence {
+ var time = submissionTime
repeat@ while (true) {
if (externalBuffer.isEmpty()) {
if (hasNext) {
@@ -220,7 +219,8 @@ public class Sc20StreamingParquetTraceReader(
for (fragment in internalBuffer) {
yield(fragment)
- if (fragment.time >= endTime) {
+ time += fragment.duration
+ if (time >= endTime) {
break@repeat
}
}
diff --git a/simulator/opendc-experiments/opendc-experiments-sc20/src/test/kotlin/org/opendc/experiments/sc20/Sc20IntegrationTest.kt b/simulator/opendc-experiments/opendc-experiments-sc20/src/test/kotlin/org/opendc/experiments/sc20/Sc20IntegrationTest.kt
index 9c44edfc..c5ad345d 100644
--- a/simulator/opendc-experiments/opendc-experiments-sc20/src/test/kotlin/org/opendc/experiments/sc20/Sc20IntegrationTest.kt
+++ b/simulator/opendc-experiments/opendc-experiments-sc20/src/test/kotlin/org/opendc/experiments/sc20/Sc20IntegrationTest.kt
@@ -48,6 +48,7 @@ import org.opendc.format.environment.EnvironmentReader
import org.opendc.format.environment.sc20.Sc20ClusterEnvironmentReader
import org.opendc.format.trace.TraceReader
import org.opendc.simulator.utils.DelayControllerClockAdapter
+import org.opendc.trace.core.EventTracer
import java.io.File
import java.time.Clock
@@ -89,7 +90,7 @@ class Sc20IntegrationTest {
fun tearDown() = testScope.cleanupTestCoroutines()
@Test
- fun smoke() {
+ fun testLarge() {
val failures = false
val seed = 0
val chan = Channel<Unit>(Channel.CONFLATED)
@@ -97,13 +98,15 @@ class Sc20IntegrationTest {
val traceReader = createTestTraceReader()
val environmentReader = createTestEnvironmentReader()
lateinit var scheduler: SimVirtProvisioningService
+ val tracer = EventTracer(clock)
testScope.launch {
val res = createProvisioner(
this,
clock,
environmentReader,
- allocationPolicy
+ allocationPolicy,
+ tracer
)
val bareMetalProvisioner = res.first
scheduler = res.second
@@ -145,28 +148,30 @@ class Sc20IntegrationTest {
assertAll(
{ assertEquals(50, scheduler.submittedVms, "The trace contains 50 VMs") },
{ assertEquals(50, scheduler.finishedVms, "All VMs should finish after a run") },
- { assertEquals(207379117949, monitor.totalRequestedBurst) },
- { assertEquals(203388071813, monitor.totalGrantedBurst) },
- { assertEquals(3991046136, monitor.totalOvercommissionedBurst) },
+ { assertEquals(1684849230562, monitor.totalRequestedBurst) },
+ { assertEquals(447612683996, monitor.totalGrantedBurst) },
+ { assertEquals(1219535757406, monitor.totalOvercommissionedBurst) },
{ assertEquals(0, monitor.totalInterferedBurst) }
)
}
@Test
- fun small() {
+ fun testSmall() {
val seed = 1
val chan = Channel<Unit>(Channel.CONFLATED)
val allocationPolicy = AvailableCoreMemoryAllocationPolicy()
val traceReader = createTestTraceReader(0.5, seed)
val environmentReader = createTestEnvironmentReader("single")
lateinit var scheduler: SimVirtProvisioningService
+ val tracer = EventTracer(clock)
testScope.launch {
val res = createProvisioner(
this,
clock,
environmentReader,
- allocationPolicy
+ allocationPolicy,
+ tracer
)
scheduler = res.second
@@ -190,10 +195,10 @@ class Sc20IntegrationTest {
// Note that these values have been verified beforehand
assertAll(
- { assertEquals(96344114723, monitor.totalRequestedBurst) },
- { assertEquals(96324378235, monitor.totalGrantedBurst) },
- { assertEquals(19736424, monitor.totalOvercommissionedBurst) },
- { assertEquals(0, monitor.totalInterferedBurst) }
+ { assertEquals(705128393966, monitor.totalRequestedBurst) { "Total requested work incorrect" } },
+ { assertEquals(173489747029, monitor.totalGrantedBurst) { "Total granted work incorrect" } },
+ { assertEquals(526858997740, monitor.totalOvercommissionedBurst) { "Total overcommitted work incorrect" } },
+ { assertEquals(0, monitor.totalInterferedBurst) { "Total interfered work incorrect" } }
)
}
diff --git a/simulator/opendc-format/src/main/kotlin/org/opendc/format/trace/bitbrains/BitbrainsTraceReader.kt b/simulator/opendc-format/src/main/kotlin/org/opendc/format/trace/bitbrains/BitbrainsTraceReader.kt
index 9353ef28..90d751ea 100644
--- a/simulator/opendc-format/src/main/kotlin/org/opendc/format/trace/bitbrains/BitbrainsTraceReader.kt
+++ b/simulator/opendc-format/src/main/kotlin/org/opendc/format/trace/bitbrains/BitbrainsTraceReader.kt
@@ -34,6 +34,7 @@ import java.io.BufferedReader
import java.io.File
import java.io.FileReader
import java.util.*
+import kotlin.math.min
/**
* A [TraceReader] for the public VM workload trace format.
@@ -70,6 +71,7 @@ public class BitbrainsTraceReader(
var vmId = -1L
var cores = -1
var requiredMemory = -1L
+ var startTime = -1L
BufferedReader(FileReader(vmFile)).use { reader ->
reader.lineSequence()
@@ -91,21 +93,17 @@ public class BitbrainsTraceReader(
}
vmId = vmFile.nameWithoutExtension.trim().toLong()
- val timestamp = values[timestampCol].trim().toLong() - 5 * 60
+ startTime = min(startTime, values[timestampCol].trim().toLong() - 5 * 60)
cores = values[coreCol].trim().toInt()
val cpuUsage = values[cpuUsageCol].trim().toDouble() // MHz
requiredMemory = (values[provisionedMemoryCol].trim().toDouble() / 1000).toLong()
- val flops: Long = (cpuUsage * 5 * 60 * cores).toLong()
-
if (flopsHistory.isEmpty()) {
- flopsHistory.add(SimTraceWorkload.Fragment(timestamp, flops, traceInterval, cpuUsage, cores))
+ flopsHistory.add(SimTraceWorkload.Fragment(traceInterval, cpuUsage, cores))
} else {
- if (flopsHistory.last().flops != flops) {
+ if (flopsHistory.last().usage != cpuUsage) {
flopsHistory.add(
SimTraceWorkload.Fragment(
- timestamp,
- flops,
traceInterval,
cpuUsage,
cores
@@ -115,8 +113,6 @@ public class BitbrainsTraceReader(
val oldFragment = flopsHistory.removeAt(flopsHistory.size - 1)
flopsHistory.add(
SimTraceWorkload.Fragment(
- oldFragment.time,
- oldFragment.flops + flops,
oldFragment.duration + traceInterval,
cpuUsage,
cores
@@ -151,7 +147,7 @@ public class BitbrainsTraceReader(
)
)
entries[vmId] = TraceEntryImpl(
- flopsHistory.firstOrNull()?.time ?: -1,
+ startTime,
vmWorkload
)
}
diff --git a/simulator/opendc-format/src/main/kotlin/org/opendc/format/trace/gwf/GwfTraceReader.kt b/simulator/opendc-format/src/main/kotlin/org/opendc/format/trace/gwf/GwfTraceReader.kt
index a20b4f29..c76889c8 100644
--- a/simulator/opendc-format/src/main/kotlin/org/opendc/format/trace/gwf/GwfTraceReader.kt
+++ b/simulator/opendc-format/src/main/kotlin/org/opendc/format/trace/gwf/GwfTraceReader.kt
@@ -29,6 +29,7 @@ import org.opendc.format.trace.TraceReader
import org.opendc.simulator.compute.workload.SimFlopsWorkload
import org.opendc.workflows.workload.Job
import org.opendc.workflows.workload.Task
+import org.opendc.workflows.workload.WORKFLOW_TASK_CORES
import org.opendc.workflows.workload.WORKFLOW_TASK_DEADLINE
import java.io.BufferedReader
import java.io.File
@@ -122,8 +123,8 @@ public class GwfTraceReader(reader: BufferedReader) : TraceReader<Job> {
val workflowId = values[workflowIdCol].trim().toLong()
val taskId = values[taskIdCol].trim().toLong()
- val submitTime = values[submitTimeCol].trim().toLong()
- val runtime = max(0, values[runtimeCol].trim().toLong())
+ val submitTime = values[submitTimeCol].trim().toLong() * 1000 // ms
+ val runtime = max(0, values[runtimeCol].trim().toLong()) // s
val cores = values[coreCol].trim().toInt()
val dependencies = values[dependencyCol].split(" ")
.filter { it.isNotEmpty() }
@@ -138,9 +139,12 @@ public class GwfTraceReader(reader: BufferedReader) : TraceReader<Job> {
val task = Task(
UUID(0L, taskId),
"<unnamed>",
- SimWorkloadImage(UUID.randomUUID(), "<unnamed>", emptyMap(), SimFlopsWorkload(flops, cores)),
+ SimWorkloadImage(UUID.randomUUID(), "<unnamed>", emptyMap(), SimFlopsWorkload(flops)),
HashSet(),
- mapOf(WORKFLOW_TASK_DEADLINE to runtime)
+ mapOf(
+ WORKFLOW_TASK_CORES to cores,
+ WORKFLOW_TASK_DEADLINE to (runtime * 1000)
+ ),
)
entry.submissionTime = min(entry.submissionTime, submitTime)
(workflow.tasks as MutableSet<Task>).add(task)
diff --git a/simulator/opendc-format/src/main/kotlin/org/opendc/format/trace/sc20/Sc20TraceReader.kt b/simulator/opendc-format/src/main/kotlin/org/opendc/format/trace/sc20/Sc20TraceReader.kt
index 66efbcd0..78f581ca 100644
--- a/simulator/opendc-format/src/main/kotlin/org/opendc/format/trace/sc20/Sc20TraceReader.kt
+++ b/simulator/opendc-format/src/main/kotlin/org/opendc/format/trace/sc20/Sc20TraceReader.kt
@@ -125,20 +125,16 @@ public class Sc20TraceReader(
requiredMemory = max(requiredMemory, values[provisionedMemoryCol].trim().toLong())
maxCores = max(maxCores, cores)
- val flops: Long = (cpuUsage * 5 * 60).toLong()
-
- last = if (last != null && last!!.flops == 0L && flops == 0L) {
+ last = if (last != null && last!!.usage == 0.0 && cpuUsage == 0.0) {
val oldFragment = last!!
SimTraceWorkload.Fragment(
- oldFragment.time,
- oldFragment.flops + flops,
oldFragment.duration + traceInterval,
cpuUsage,
cores
)
} else {
val fragment =
- SimTraceWorkload.Fragment(timestamp, flops, traceInterval, cpuUsage, cores)
+ SimTraceWorkload.Fragment(traceInterval, cpuUsage, cores)
if (last != null) {
yield(last!!)
}
diff --git a/simulator/opendc-format/src/main/kotlin/org/opendc/format/trace/swf/SwfTraceReader.kt b/simulator/opendc-format/src/main/kotlin/org/opendc/format/trace/swf/SwfTraceReader.kt
index 52d41c44..80c54354 100644
--- a/simulator/opendc-format/src/main/kotlin/org/opendc/format/trace/swf/SwfTraceReader.kt
+++ b/simulator/opendc-format/src/main/kotlin/org/opendc/format/trace/swf/SwfTraceReader.kt
@@ -113,8 +113,6 @@ public class SwfTraceReader(
for (tick in submitTime until (submitTime + waitTime - sliceDuration) step sliceDuration) {
flopsHistory.add(
SimTraceWorkload.Fragment(
- tick * 1000L,
- 0L,
sliceDuration * 1000L,
0.0,
cores
@@ -138,8 +136,6 @@ public class SwfTraceReader(
) {
flopsHistory.add(
SimTraceWorkload.Fragment(
- tick * 1000L,
- flopsFullSlice / sliceDuration,
sliceDuration * 1000L,
1.0,
cores
@@ -150,8 +146,6 @@ public class SwfTraceReader(
if (runtimePartialSliceRemainder > 0) {
flopsHistory.add(
SimTraceWorkload.Fragment(
- submitTime + (slicedWaitTime + runTime - runtimePartialSliceRemainder),
- flopsPartialSlice,
sliceDuration,
runtimePartialSliceRemainder / sliceDuration.toDouble(),
cores
diff --git a/simulator/opendc-format/src/main/kotlin/org/opendc/format/trace/wtf/WtfTraceReader.kt b/simulator/opendc-format/src/main/kotlin/org/opendc/format/trace/wtf/WtfTraceReader.kt
index b2931468..d7dc09fa 100644
--- a/simulator/opendc-format/src/main/kotlin/org/opendc/format/trace/wtf/WtfTraceReader.kt
+++ b/simulator/opendc-format/src/main/kotlin/org/opendc/format/trace/wtf/WtfTraceReader.kt
@@ -32,6 +32,7 @@ import org.opendc.format.trace.TraceReader
import org.opendc.simulator.compute.workload.SimFlopsWorkload
import org.opendc.workflows.workload.Job
import org.opendc.workflows.workload.Task
+import org.opendc.workflows.workload.WORKFLOW_TASK_CORES
import org.opendc.workflows.workload.WORKFLOW_TASK_DEADLINE
import java.util.UUID
import kotlin.math.min
@@ -80,9 +81,12 @@ public class WtfTraceReader(path: String) : TraceReader<Job> {
val task = Task(
UUID(0L, taskId),
"<unnamed>",
- SimWorkloadImage(UUID.randomUUID(), "<unnamed>", emptyMap(), SimFlopsWorkload(flops, cores)),
+ SimWorkloadImage(UUID.randomUUID(), "<unnamed>", emptyMap(), SimFlopsWorkload(flops)),
HashSet(),
- mapOf(WORKFLOW_TASK_DEADLINE to runtime)
+ mapOf(
+ WORKFLOW_TASK_CORES to cores,
+ WORKFLOW_TASK_DEADLINE to runtime
+ )
)
entry.submissionTime = min(entry.submissionTime, submitTime)
diff --git a/simulator/opendc-format/src/test/kotlin/org/opendc/format/trace/swf/SwfTraceReaderTest.kt b/simulator/opendc-format/src/test/kotlin/org/opendc/format/trace/swf/SwfTraceReaderTest.kt
index 8db2ab40..45c125c4 100644
--- a/simulator/opendc-format/src/test/kotlin/org/opendc/format/trace/swf/SwfTraceReaderTest.kt
+++ b/simulator/opendc-format/src/test/kotlin/org/opendc/format/trace/swf/SwfTraceReaderTest.kt
@@ -41,7 +41,6 @@ class SwfTraceReaderTest {
assertEquals(164472, entry.submissionTime)
// 1188 slices for waiting, 0 full and 1 partial running slices
assertEquals(1189, ((entry.workload.image as SimWorkloadImage).workload as SimTraceWorkload).trace.toList().size)
- assertEquals(5_100_000L, ((entry.workload.image as SimWorkloadImage).workload as SimTraceWorkload).trace.toList().last().flops)
assertEquals(0.25, ((entry.workload.image as SimWorkloadImage).workload as SimTraceWorkload).trace.toList().last().usage)
}
}
diff --git a/simulator/opendc-harness/build.gradle.kts b/simulator/opendc-harness/build.gradle.kts
new file mode 100644
index 00000000..359d2384
--- /dev/null
+++ b/simulator/opendc-harness/build.gradle.kts
@@ -0,0 +1,47 @@
+/*
+ * 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 = "Harness for defining repeatable experiments using OpenDC"
+
+/* Build configuration */
+plugins {
+ `kotlin-library-convention`
+}
+
+dependencies {
+ api("org.jetbrains.kotlinx:kotlinx-coroutines-core:${Library.KOTLINX_COROUTINES}")
+ api("org.junit.platform:junit-platform-commons:${Library.JUNIT_PLATFORM}")
+
+ implementation("io.github.classgraph:classgraph:4.8.98")
+ implementation("me.tongfei:progressbar:0.9.0")
+ implementation("io.github.microutils:kotlin-logging:2.0.4")
+ implementation("com.github.ajalt.clikt:clikt:3.1.0")
+
+ api("org.junit.platform:junit-platform-engine:${Library.JUNIT_PLATFORM}")
+ api("org.junit.platform:junit-platform-suite-api:${Library.JUNIT_PLATFORM}")
+ api("org.junit.platform:junit-platform-launcher:${Library.JUNIT_PLATFORM}")
+
+ runtimeOnly("org.apache.logging.log4j:log4j-slf4j-impl:2.14.0")
+
+ 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-harness/src/main/kotlin/org/opendc/harness/api/ExperimentDefinition.kt b/simulator/opendc-harness/src/main/kotlin/org/opendc/harness/api/ExperimentDefinition.kt
new file mode 100644
index 00000000..88b26ee1
--- /dev/null
+++ b/simulator/opendc-harness/src/main/kotlin/org/opendc/harness/api/ExperimentDefinition.kt
@@ -0,0 +1,39 @@
+/*
+ * 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.harness.api
+
+/**
+ * A definition for a repeatable experiment which consists of multiple scenarios derived from pre-defined experiment
+ * parameters.
+ *
+ * @property name The name of the experiment.
+ * @property parameters The parameters of the experiments.
+ * @property evaluator The function to evaluate a single experiment trial.
+ * @property meta The metadata for the experiment.
+ */
+public data class ExperimentDefinition(
+ val name: String,
+ val parameters: Set<Parameter<*>>,
+ val evaluator: (Trial) -> Unit,
+ val meta: Map<String, Any> = emptyMap()
+)
diff --git a/simulator/opendc-experiments/opendc-experiments-sc20/src/main/kotlin/org/opendc/experiments/sc20/runner/execution/ExperimentExecutionResult.kt b/simulator/opendc-harness/src/main/kotlin/org/opendc/harness/api/Parameter.kt
index a765c264..bb5c8c2b 100644
--- a/simulator/opendc-experiments/opendc-experiments-sc20/src/main/kotlin/org/opendc/experiments/sc20/runner/execution/ExperimentExecutionResult.kt
+++ b/simulator/opendc-harness/src/main/kotlin/org/opendc/harness/api/Parameter.kt
@@ -1,7 +1,5 @@
/*
- * MIT License
- *
- * Copyright (c) 2020 atlarge-research
+ * 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
@@ -22,21 +20,19 @@
* SOFTWARE.
*/
-package org.opendc.experiments.sc20.runner.execution
-
-import java.io.Serializable
+package org.opendc.harness.api
/**
- * The result of executing an experiment.
+ * A [Parameter] defines a single dimension of exploration in the design space of an experiment.
*/
-public sealed class ExperimentExecutionResult : Serializable {
+public sealed class Parameter<T> {
/**
- * The experiment executed successfully
+ * The name of the parameter.
*/
- public object Success : ExperimentExecutionResult()
+ public abstract val name: String
/**
- * The experiment failed during execution.
+ * A generic dimension of the experiment design space that is defined fully by a collection of [values].
*/
- public data class Failed(val throwable: Throwable) : ExperimentExecutionResult()
+ public data class Generic<T>(override val name: String, val values: Collection<T>) : Parameter<T>()
}
diff --git a/simulator/opendc-harness/src/main/kotlin/org/opendc/harness/api/Scenario.kt b/simulator/opendc-harness/src/main/kotlin/org/opendc/harness/api/Scenario.kt
new file mode 100644
index 00000000..a8dbf01e
--- /dev/null
+++ b/simulator/opendc-harness/src/main/kotlin/org/opendc/harness/api/Scenario.kt
@@ -0,0 +1,46 @@
+/*
+ * 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.harness.api
+
+/**
+ * A [Scenario] represents a single point in the design space of an experiment.
+ */
+public interface Scenario {
+ /**
+ * A unique identifier that identifies a single scenario.
+ */
+ public val id: Int
+
+ /**
+ * The [ExperimentDefinition] describing the experiment this scenario is part of.
+ */
+ public val experiment: ExperimentDefinition
+
+ /**
+ * Obtain the instantiated value for a [parameter][param] of the experiment.
+ *
+ * @param param The parameter to obtain the value of.
+ * @throws IllegalArgumentException if [param] is not defined for the experiment.
+ */
+ public operator fun <T> get(param: Parameter<T>): T
+}
diff --git a/simulator/opendc-harness/src/main/kotlin/org/opendc/harness/api/Trial.kt b/simulator/opendc-harness/src/main/kotlin/org/opendc/harness/api/Trial.kt
new file mode 100644
index 00000000..2d6ecd19
--- /dev/null
+++ b/simulator/opendc-harness/src/main/kotlin/org/opendc/harness/api/Trial.kt
@@ -0,0 +1,28 @@
+/*
+ * 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.harness.api
+
+/**
+ * A [Trial] represents a single trial (run) of an experiment.
+ */
+public data class Trial(val scenario: Scenario, val repeat: Int)
diff --git a/simulator/opendc-harness/src/main/kotlin/org/opendc/harness/dsl/Experiment.kt b/simulator/opendc-harness/src/main/kotlin/org/opendc/harness/dsl/Experiment.kt
new file mode 100644
index 00000000..41d4207a
--- /dev/null
+++ b/simulator/opendc-harness/src/main/kotlin/org/opendc/harness/dsl/Experiment.kt
@@ -0,0 +1,99 @@
+/*
+ * 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.harness.dsl
+
+import org.junit.platform.commons.annotation.Testable
+import org.opendc.harness.api.ExperimentDefinition
+import org.opendc.harness.api.Scenario
+import org.opendc.harness.api.Trial
+import org.opendc.harness.internal.ParameterDelegate
+
+/**
+ * An [Experiment] defines a blueprint for a repeatable experiment consisting of multiple scenarios based on pre-defined
+ * experiment parameters.
+ *
+ * @param name The name of the experiment or `null` to select the class name.
+ */
+@Testable
+public abstract class Experiment(name: String? = null) : Cloneable {
+ /**
+ * The name of the experiment.
+ */
+ public val name: String = name ?: javaClass.simpleName
+
+ /**
+ * An identifier that uniquely identifies a single point in the design space of this [Experiment].
+ */
+ public val id: Int
+ get() {
+ val scenario = scenario ?: throw IllegalStateException("Cannot use id before activation")
+ return scenario.id
+ }
+
+ /**
+ * Convert this experiment to an [ExperimentDefinition].
+ */
+ public fun toDefinition(): ExperimentDefinition =
+ ExperimentDefinition(name, HashSet(delegates.map { it.parameter }), this::run, mapOf("class.name" to javaClass.name))
+
+ /**
+ * Perform a single execution of the experiment based on the experiment parameters.
+ *
+ * @param repeat A number representing the repeat index of an identical scenario.
+ */
+ protected abstract fun doRun(repeat: Int)
+
+ /**
+ * A map to track the parameter delegates registered with this experiment.
+ */
+ private val delegates: MutableSet<ParameterDelegate<*>> = mutableSetOf()
+
+ /**
+ * The current active scenario.
+ */
+ internal var scenario: Scenario? = null
+
+ /**
+ * Perform a single execution of the experiment based on the experiment parameters.
+ *
+ * This operation will cause the [ParameterProvider]s of this group to be instantiated to some value in its design
+ * space.
+ *
+ * @param trial The experiment trial to run.
+ */
+ private fun run(trial: Trial) {
+ val scenario = trial.scenario
+
+ // XXX We clone the current class to prevent concurrency issues.
+ val res = clone() as Experiment
+ res.scenario = scenario
+ res.doRun(trial.repeat)
+ }
+
+ /**
+ * Register a delegate for this experiment.
+ */
+ internal fun register(delegate: ParameterDelegate<*>) {
+ delegates += delegate
+ }
+}
diff --git a/simulator/opendc-harness/src/main/kotlin/org/opendc/harness/dsl/ParameterProvider.kt b/simulator/opendc-harness/src/main/kotlin/org/opendc/harness/dsl/ParameterProvider.kt
new file mode 100644
index 00000000..e4bb9c64
--- /dev/null
+++ b/simulator/opendc-harness/src/main/kotlin/org/opendc/harness/dsl/ParameterProvider.kt
@@ -0,0 +1,39 @@
+/*
+ * 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.harness.dsl
+
+import kotlin.properties.ReadOnlyProperty
+import kotlin.reflect.KProperty
+
+/**
+ * A [ParameterProvider] defines a single dimension of exploration in the design space of an [Experiment].
+ */
+public interface ParameterProvider<T> {
+ /**
+ * Provide a delegate defining a parameter for the specified [Experiment][experiment].
+ *
+ * @param experiment The experiment for which the parameter is defined.
+ * @param prop The property to create the delegate for.
+ */
+ public operator fun provideDelegate(experiment: Experiment, prop: KProperty<*>): ReadOnlyProperty<Experiment, T>
+}
diff --git a/simulator/opendc-harness/src/main/kotlin/org/opendc/harness/dsl/Parameters.kt b/simulator/opendc-harness/src/main/kotlin/org/opendc/harness/dsl/Parameters.kt
new file mode 100644
index 00000000..7d269ba1
--- /dev/null
+++ b/simulator/opendc-harness/src/main/kotlin/org/opendc/harness/dsl/Parameters.kt
@@ -0,0 +1,44 @@
+/*
+ * Copyright (c) 2021 AtLarge Research
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
+ * of this software and associated documentation files (the "Software"), to deal
+ * in the Software without restriction, including without limitation the rights
+ * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+ * copies of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included in all
+ * copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
+ * SOFTWARE.
+ */
+
+package org.opendc.harness.dsl
+
+import org.opendc.harness.api.Parameter
+import org.opendc.harness.internal.ParameterDelegate
+import kotlin.properties.ReadOnlyProperty
+import kotlin.reflect.KProperty
+
+/**
+ * Define a dimension in the design space of an [Experiment] of type [T] consisting of the specified collection of
+ * [values].
+ *
+ * @param values The values in the dimension to define..
+ */
+public fun <T> anyOf(vararg values: T): ParameterProvider<T> = object : ParameterProvider<T> {
+ override fun provideDelegate(experiment: Experiment, prop: KProperty<*>): ReadOnlyProperty<Experiment, T> {
+ val delegate = ParameterDelegate(Parameter.Generic(prop.name, listOf(*values)))
+ experiment.register(delegate)
+ return delegate
+ }
+
+ override fun toString(): String = "GenericParameter"
+}
diff --git a/simulator/opendc-harness/src/main/kotlin/org/opendc/harness/engine/ExperimentEngine.kt b/simulator/opendc-harness/src/main/kotlin/org/opendc/harness/engine/ExperimentEngine.kt
new file mode 100644
index 00000000..65a0604d
--- /dev/null
+++ b/simulator/opendc-harness/src/main/kotlin/org/opendc/harness/engine/ExperimentEngine.kt
@@ -0,0 +1,94 @@
+/*
+ * 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.harness.engine
+
+import kotlinx.coroutines.*
+import kotlinx.coroutines.flow.asFlow
+import kotlinx.coroutines.flow.buffer
+import kotlinx.coroutines.flow.collect
+import kotlinx.coroutines.flow.map
+import org.opendc.harness.api.ExperimentDefinition
+import org.opendc.harness.api.Trial
+import org.opendc.harness.engine.scheduler.ExperimentScheduler
+import org.opendc.harness.engine.strategy.ExperimentStrategy
+
+/**
+ * The [ExperimentEngine] orchestrates the execution of experiments.
+ *
+ * @property strategy The [ExperimentStrategy] used to explore the experiment design space.
+ * @property scheduler The [ExperimentScheduler] to schedule the trials over compute resources.
+ * @property listener The [ExperimentExecutionListener] to observe the progress.
+ * @property repeats The number of repeats to perform.
+ */
+public class ExperimentEngine(
+ private val strategy: ExperimentStrategy,
+ private val scheduler: ExperimentScheduler,
+ private val listener: ExperimentExecutionListener,
+ private val repeats: Int
+) {
+ /**
+ * Execute the specified [experiment][root].
+ *
+ * @param root The experiment to execute.
+ */
+ @OptIn(InternalCoroutinesApi::class)
+ public suspend fun execute(root: ExperimentDefinition): Unit = supervisorScope {
+ listener.experimentStarted(root)
+
+ try {
+ strategy.generate(root)
+ .asFlow()
+ .map { scenario ->
+ listener.scenarioStarted(scenario)
+ scenario
+ }
+ .buffer(100)
+ .collect { scenario ->
+ val jobs = (0 until repeats).map { repeat ->
+ val worker = scheduler.allocate()
+ launch {
+ val trial = Trial(scenario, repeat)
+ try {
+ listener.trialStarted(trial)
+ worker.dispatch(trial)
+ listener.trialFinished(trial, null)
+ } catch (e: Throwable) {
+ listener.trialFinished(trial, e)
+ }
+ }
+ }
+
+ launch {
+ jobs.joinAll()
+ listener.scenarioFinished(scenario, null)
+ }
+ }
+ listener.experimentFinished(root, null)
+ } catch (e: Throwable) {
+ listener.experimentFinished(root, e)
+ throw e
+ }
+ }
+
+ override fun toString(): String = "ExperimentEngine"
+}
diff --git a/simulator/opendc-harness/src/main/kotlin/org/opendc/harness/engine/ExperimentEngineLauncher.kt b/simulator/opendc-harness/src/main/kotlin/org/opendc/harness/engine/ExperimentEngineLauncher.kt
new file mode 100644
index 00000000..ddd30483
--- /dev/null
+++ b/simulator/opendc-harness/src/main/kotlin/org/opendc/harness/engine/ExperimentEngineLauncher.kt
@@ -0,0 +1,121 @@
+/*
+ * 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.harness.engine
+
+import kotlinx.coroutines.flow.Flow
+import kotlinx.coroutines.flow.collect
+import kotlinx.coroutines.runBlocking
+import org.opendc.harness.api.ExperimentDefinition
+import org.opendc.harness.engine.scheduler.ExperimentScheduler
+import org.opendc.harness.engine.scheduler.ThreadPoolExperimentScheduler
+import org.opendc.harness.engine.strategy.CartesianExperimentStrategy
+import org.opendc.harness.engine.strategy.ExperimentStrategy
+import org.opendc.harness.internal.CompositeExperimentExecutionListener
+
+/**
+ * A builder class for conducting experiments via the [ExperimentEngine].
+ */
+public class ExperimentEngineLauncher private constructor(
+ private val strategy: ExperimentStrategy?,
+ private val scheduler: ExperimentScheduler?,
+ private val listeners: List<ExperimentExecutionListener>,
+ private val repeats: Int
+) {
+ /**
+ * Construct an [ExperimentEngineLauncher] instance.
+ */
+ public constructor() : this(null, null, emptyList(), 1)
+
+ /**
+ * Create an [ExperimentEngineLauncher] with the specified [strategy].
+ */
+ public fun withScheduler(strategy: ExperimentStrategy): ExperimentEngineLauncher {
+ return ExperimentEngineLauncher(strategy, scheduler, listeners, repeats)
+ }
+
+ /**
+ * Create an [ExperimentEngineLauncher] with the specified [scheduler].
+ */
+ public fun withScheduler(scheduler: ExperimentScheduler): ExperimentEngineLauncher {
+ return ExperimentEngineLauncher(strategy, scheduler, listeners, repeats)
+ }
+
+ /**
+ * Create an [ExperimentEngineLauncher] with the specified [listener] added.
+ */
+ public fun withListener(listener: ExperimentExecutionListener): ExperimentEngineLauncher {
+ return ExperimentEngineLauncher(strategy, scheduler, listeners + listener, repeats)
+ }
+
+ /**
+ * Create an [ExperimentEngineLauncher] with the specified number of repeats.
+ */
+ public fun withRepeats(repeats: Int): ExperimentEngineLauncher {
+ require(repeats > 0) { "Invalid number of repeats; must be greater than zero. " }
+ return ExperimentEngineLauncher(strategy, scheduler, listeners, repeats)
+ }
+
+ /**
+ * Launch the specified experiments via the [ExperimentEngine] and block execution until finished.
+ */
+ public suspend fun run(experiments: Flow<ExperimentDefinition>) {
+ val engine = ExperimentEngine(createStrategy(), createScheduler(), createListener(), repeats)
+ experiments.collect { experiment -> engine.execute(experiment) }
+ }
+
+ /**
+ * Launch the specified experiments via the [ExperimentEngine] and block the current thread until finished.
+ */
+ public fun runBlocking(experiments: Flow<ExperimentDefinition>) {
+ runBlocking {
+ run(experiments)
+ }
+ }
+
+ /**
+ * Return a string representation of this instance.
+ */
+ public override fun toString(): String = "ExperimentEngineLauncher"
+
+ /**
+ * Create the [ExperimentStrategy] that explores the experiment design space.
+ */
+ private fun createStrategy(): ExperimentStrategy {
+ return strategy ?: CartesianExperimentStrategy
+ }
+
+ /**
+ * Create the [ExperimentScheduler] that schedules the trials over the compute resources.
+ */
+ private fun createScheduler(): ExperimentScheduler {
+ return scheduler ?: ThreadPoolExperimentScheduler(Runtime.getRuntime().availableProcessors())
+ }
+
+ /**
+ * Create the [ExperimentExecutionListener] that listens the to the execution of the experiments.
+ */
+ private fun createListener(): ExperimentExecutionListener {
+ require(listeners.isNotEmpty()) { "No listeners registered." }
+ return CompositeExperimentExecutionListener(listeners)
+ }
+}
diff --git a/simulator/opendc-harness/src/main/kotlin/org/opendc/harness/engine/ExperimentExecutionListener.kt b/simulator/opendc-harness/src/main/kotlin/org/opendc/harness/engine/ExperimentExecutionListener.kt
new file mode 100644
index 00000000..9ef71863
--- /dev/null
+++ b/simulator/opendc-harness/src/main/kotlin/org/opendc/harness/engine/ExperimentExecutionListener.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.harness.engine
+
+import org.opendc.harness.api.ExperimentDefinition
+import org.opendc.harness.api.Scenario
+import org.opendc.harness.api.Trial
+
+/**
+ * Listener to be notified of experiment execution events by experiment runners.
+ */
+public interface ExperimentExecutionListener {
+ /**
+ * A method that is invoked when an experiment is started.
+ *
+ * @param experiment The [ExperimentDefinition] that started.
+ */
+ public fun experimentStarted(experiment: ExperimentDefinition) {}
+
+ /**
+ * A method that is invoked when an experiment is finished, regardless of the outcome.
+ *
+ * @param experiment The [ExperimentDefinition] that finished.
+ * @param throwable The exception that was thrown during execution or `null` if the execution completed successfully.
+ */
+ public fun experimentFinished(experiment: ExperimentDefinition, throwable: Throwable?) {}
+
+ /**
+ * A method that is invoked when a scenario is started.
+ *
+ * @param scenario The scenario that is started.
+ */
+ public fun scenarioStarted(scenario: Scenario) {}
+
+ /**
+ * A method that is invoked when a scenario is finished, regardless of the outcome.
+ *
+ * @param scenario The [Scenario] that has finished.
+ * @param throwable The exception that was thrown during execution or `null` if the execution completed successfully.
+ */
+ public fun scenarioFinished(scenario: Scenario, throwable: Throwable?) {}
+
+ /**
+ * A method that is invoked when a trial is started.
+ *
+ * @param trial The trial that is started.
+ */
+ public fun trialStarted(trial: Trial) {}
+
+ /**
+ * A method that is invoked when a scenario is finished, regardless of the outcome.
+ *
+ * @param trial The [Trial] that has finished.
+ * @param throwable The exception that was thrown during execution or `null` if the execution completed successfully.
+ */
+ public fun trialFinished(trial: Trial, throwable: Throwable?) {}
+}
diff --git a/simulator/opendc-harness/src/main/kotlin/org/opendc/harness/engine/discovery/Discovery.kt b/simulator/opendc-harness/src/main/kotlin/org/opendc/harness/engine/discovery/Discovery.kt
new file mode 100644
index 00000000..f7f73b38
--- /dev/null
+++ b/simulator/opendc-harness/src/main/kotlin/org/opendc/harness/engine/discovery/Discovery.kt
@@ -0,0 +1,39 @@
+/*
+ * 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.harness.engine.discovery
+
+import kotlinx.coroutines.flow.Flow
+import org.opendc.harness.api.ExperimentDefinition
+
+/**
+ * Component responsible for scanning for [ExperimentDefinition]s.
+ */
+public interface Discovery {
+ /**
+ * Start discovery of experiments.
+ *
+ * @param request The [DiscoveryRequest] to determine the experiments to discover.
+ * @return A flow of [ExperimentDefinition]s that have been discovered by the implementation.
+ */
+ public fun discover(request: DiscoveryRequest): Flow<ExperimentDefinition>
+}
diff --git a/simulator/opendc-harness/src/main/kotlin/org/opendc/harness/engine/discovery/DiscoveryFilter.kt b/simulator/opendc-harness/src/main/kotlin/org/opendc/harness/engine/discovery/DiscoveryFilter.kt
new file mode 100644
index 00000000..219d09cd
--- /dev/null
+++ b/simulator/opendc-harness/src/main/kotlin/org/opendc/harness/engine/discovery/DiscoveryFilter.kt
@@ -0,0 +1,51 @@
+/*
+ * Copyright (c) 2021 AtLarge Research
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
+ * of this software and associated documentation files (the "Software"), to deal
+ * in the Software without restriction, including without limitation the rights
+ * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+ * copies of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included in all
+ * copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
+ * SOFTWARE.
+ */
+
+package org.opendc.harness.engine.discovery
+
+import org.opendc.harness.api.ExperimentDefinition
+import java.util.function.Predicate
+
+/**
+ * A [DiscoveryFilter] decides how the selected experiments are filtered.
+ */
+public sealed class DiscoveryFilter {
+ /**
+ * Test whether the specified [ExperimentDefinition] should be selected.
+ */
+ public abstract fun test(definition: ExperimentDefinition): Boolean
+
+ /**
+ * Filter an experiment based on its name.
+ */
+ public data class Name(val predicate: Predicate<String>) : DiscoveryFilter() {
+ override fun test(definition: ExperimentDefinition): Boolean = predicate.test(definition.name)
+ }
+
+ /**
+ * Filter an experiment based on its metadata.
+ */
+ public data class Meta(val key: String, val predicate: Predicate<Any>) : DiscoveryFilter() {
+ override fun test(definition: ExperimentDefinition): Boolean =
+ definition.meta[key]?.let { predicate.test(it) } ?: false
+ }
+}
diff --git a/simulator/opendc-harness/src/main/kotlin/org/opendc/harness/engine/discovery/DiscoveryProvider.kt b/simulator/opendc-harness/src/main/kotlin/org/opendc/harness/engine/discovery/DiscoveryProvider.kt
new file mode 100644
index 00000000..fad255de
--- /dev/null
+++ b/simulator/opendc-harness/src/main/kotlin/org/opendc/harness/engine/discovery/DiscoveryProvider.kt
@@ -0,0 +1,65 @@
+/*
+ * 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.harness.engine.discovery
+
+import org.opendc.harness.internal.CompositeDiscovery
+import java.util.*
+
+/**
+ * A provider interface for the [Discovery] component.
+ */
+public interface DiscoveryProvider {
+ /**
+ * A unique identifier for this discovery implementation.
+ *
+ * Each discovery implementation must provide a unique ID, so that they can be selected by the user.
+ * When in doubt, you may use the fully qualified name of your custom [Discovery] implementation class.
+ */
+ public val id: String
+
+ /**
+ * Factory method for creating a new [Discovery] instance.
+ */
+ public fun create(): Discovery
+
+ public companion object {
+ /**
+ * The available [DiscoveryProvider]s.
+ */
+ private val providers by lazy { ServiceLoader.load(DiscoveryProvider::class.java) }
+
+ /**
+ * Obtain the [DiscoveryProvider] with the specified [id] or return `null`.
+ */
+ public fun findById(id: String): DiscoveryProvider? {
+ return providers.find { it.id == id }
+ }
+
+ /**
+ * Obtain a composite [Discovery] that combines the results of all available providers.
+ */
+ public fun createComposite(): Discovery {
+ return CompositeDiscovery(providers)
+ }
+ }
+}
diff --git a/simulator/opendc-harness/src/main/kotlin/org/opendc/harness/engine/discovery/DiscoveryRequest.kt b/simulator/opendc-harness/src/main/kotlin/org/opendc/harness/engine/discovery/DiscoveryRequest.kt
new file mode 100644
index 00000000..5bc08dac
--- /dev/null
+++ b/simulator/opendc-harness/src/main/kotlin/org/opendc/harness/engine/discovery/DiscoveryRequest.kt
@@ -0,0 +1,34 @@
+/*
+ * 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.harness.engine.discovery
+
+/**
+ * A request for discovering experiments according to the specified information.
+ *
+ * @param selectors The selectors for this discovery request.
+ * @param filters The filters for this discovery request.
+ */
+public data class DiscoveryRequest(
+ val selectors: List<DiscoverySelector> = emptyList(),
+ val filters: List<DiscoveryFilter> = emptyList(),
+)
diff --git a/simulator/opendc-experiments/opendc-experiments-sc20/src/main/kotlin/org/opendc/experiments/sc20/runner/execution/ExperimentExecutionListener.kt b/simulator/opendc-harness/src/main/kotlin/org/opendc/harness/engine/discovery/DiscoverySelector.kt
index 42fef164..67681303 100644
--- a/simulator/opendc-experiments/opendc-experiments-sc20/src/main/kotlin/org/opendc/experiments/sc20/runner/execution/ExperimentExecutionListener.kt
+++ b/simulator/opendc-harness/src/main/kotlin/org/opendc/harness/engine/discovery/DiscoverySelector.kt
@@ -1,5 +1,5 @@
/*
- * Copyright (c) 2020 AtLarge Research
+ * Copyright (c) 2021 AtLarge Research
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to deal
@@ -20,27 +20,30 @@
* SOFTWARE.
*/
-package org.opendc.experiments.sc20.runner.execution
+package org.opendc.harness.engine.discovery
-import org.opendc.experiments.sc20.runner.ExperimentDescriptor
+import org.opendc.harness.api.ExperimentDefinition
/**
- * Listener to be notified of experiment execution events by experiment runners.
+ * A [DiscoverySelector] defines the properties used to discover experiments.
*/
-public interface ExperimentExecutionListener {
+public sealed class DiscoverySelector {
/**
- * A method that is invoked when a new [ExperimentDescriptor] is registered.
+ * Test whether the specified [ExperimentDefinition] should be selected.
*/
- public fun descriptorRegistered(descriptor: ExperimentDescriptor)
+ public abstract fun test(definition: ExperimentDefinition): Boolean
/**
- * A method that is invoked when when the execution of a leaf or subtree of the experiment tree has finished,
- * regardless of the outcome.
+ * Select an experiment based on its name.
*/
- public fun executionFinished(descriptor: ExperimentDescriptor, result: ExperimentExecutionResult)
+ public data class Name(val name: String) : DiscoverySelector() {
+ override fun test(definition: ExperimentDefinition): Boolean = definition.name == name
+ }
/**
- * A method that is invoked when the execution of a leaf or subtree of the experiment tree is about to be started.
+ * Select an experiment based on its metadata.
*/
- public fun executionStarted(descriptor: ExperimentDescriptor)
+ public data class Meta(val key: String, val value: Any) : DiscoverySelector() {
+ override fun test(definition: ExperimentDefinition): Boolean = definition.meta[key] == value
+ }
}
diff --git a/simulator/opendc-experiments/opendc-experiments-sc20/src/main/kotlin/org/opendc/experiments/sc20/runner/execution/ExperimentScheduler.kt b/simulator/opendc-harness/src/main/kotlin/org/opendc/harness/engine/scheduler/ExperimentScheduler.kt
index 70095ccd..0265554a 100644
--- a/simulator/opendc-experiments/opendc-experiments-sc20/src/main/kotlin/org/opendc/experiments/sc20/runner/execution/ExperimentScheduler.kt
+++ b/simulator/opendc-harness/src/main/kotlin/org/opendc/harness/engine/scheduler/ExperimentScheduler.kt
@@ -1,5 +1,5 @@
/*
- * Copyright (c) 2020 AtLarge Research
+ * Copyright (c) 2021 AtLarge Research
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to deal
@@ -20,37 +20,33 @@
* SOFTWARE.
*/
-package org.opendc.experiments.sc20.runner.execution
+package org.opendc.harness.engine.scheduler
-import org.opendc.experiments.sc20.runner.ExperimentDescriptor
-import java.io.Closeable
+import org.opendc.harness.api.Trial
/**
- * A interface for scheduling the execution of experiment trials over compute resources (threads/containers/vms)
+ * The [ExperimentScheduler] is responsible for scheduling the execution of experiment runs over some set of compute
+ * resources (e.g., threads or even multiple machines).
*/
-public interface ExperimentScheduler : Closeable {
+public interface ExperimentScheduler : AutoCloseable {
/**
* Allocate a [Worker] for executing an experiment trial. This method may suspend in case no resources are directly
* available at the moment.
*
* @return The available worker.
*/
- public suspend fun allocate(): ExperimentScheduler.Worker
+ public suspend fun allocate(): Worker
/**
- * An isolated worker of an [ExperimentScheduler] that is responsible for executing a single experiment trial.
+ * An isolated worker of an [ExperimentScheduler] that is responsible for conducting a single experiment trial.
*/
public interface Worker {
/**
- * Dispatch the specified [ExperimentDescriptor] to execute some time in the future and return the results of
- * the trial.
+ * Dispatch an experiment trial immediately to one of the available compute resources and block execution until
+ * the trial has finished.
*
- * @param descriptor The descriptor to execute.
- * @param context The context to execute the descriptor in.
+ * @param trial The trial to dispatch.
*/
- public suspend operator fun invoke(
- descriptor: ExperimentDescriptor,
- context: ExperimentExecutionContext
- )
+ public suspend fun dispatch(trial: Trial)
}
}
diff --git a/simulator/opendc-harness/src/main/kotlin/org/opendc/harness/engine/scheduler/ExperimentSchedulerProvider.kt b/simulator/opendc-harness/src/main/kotlin/org/opendc/harness/engine/scheduler/ExperimentSchedulerProvider.kt
new file mode 100644
index 00000000..a93d4bf6
--- /dev/null
+++ b/simulator/opendc-harness/src/main/kotlin/org/opendc/harness/engine/scheduler/ExperimentSchedulerProvider.kt
@@ -0,0 +1,57 @@
+/*
+ * 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.harness.engine.scheduler
+
+import java.util.*
+
+/**
+ * A factory for constructing an [ExperimentScheduler].
+ */
+public interface ExperimentSchedulerProvider {
+ /**
+ * A unique identifier for this scheduler implementation.
+ *
+ * Each experiment scheduler must provide a unique ID, so that they can be selected by the user.
+ * When in doubt, you may use the fully qualified name of your custom [ExperimentScheduler] implementation class.
+ */
+ public val id: String
+
+ /**
+ * Factory method for creating a new [ExperimentScheduler] instance.
+ */
+ public fun create(): ExperimentScheduler
+
+ public companion object {
+ /**
+ * The available [ExperimentSchedulerProvider]s.
+ */
+ private val providers by lazy { ServiceLoader.load(ExperimentSchedulerProvider::class.java) }
+
+ /**
+ * Obtain the [ExperimentScheduler] with the specified [id] or return `null`.
+ */
+ public fun findById(id: String): ExperimentSchedulerProvider? {
+ return providers.find { it.id == id }
+ }
+ }
+}
diff --git a/simulator/opendc-harness/src/main/kotlin/org/opendc/harness/engine/scheduler/ThreadPoolExperimentScheduler.kt b/simulator/opendc-harness/src/main/kotlin/org/opendc/harness/engine/scheduler/ThreadPoolExperimentScheduler.kt
new file mode 100644
index 00000000..1ae533cf
--- /dev/null
+++ b/simulator/opendc-harness/src/main/kotlin/org/opendc/harness/engine/scheduler/ThreadPoolExperimentScheduler.kt
@@ -0,0 +1,58 @@
+/*
+ * 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.harness.engine.scheduler
+
+import kotlinx.coroutines.asCoroutineDispatcher
+import kotlinx.coroutines.sync.Semaphore
+import kotlinx.coroutines.withContext
+import org.opendc.harness.api.Trial
+import java.util.concurrent.Executors
+
+/**
+ * An [ExperimentScheduler] that runs experiment trials using a local thread pool.
+ *
+ * @param parallelism The maximum amount of concurrent workers.
+ */
+public class ThreadPoolExperimentScheduler(parallelism: Int) : ExperimentScheduler {
+ private val dispatcher = Executors.newCachedThreadPool().asCoroutineDispatcher()
+ private val tickets = Semaphore(parallelism)
+
+ override suspend fun allocate(): ExperimentScheduler.Worker {
+ tickets.acquire()
+ return object : ExperimentScheduler.Worker {
+ override suspend fun dispatch(trial: Trial) {
+ try {
+ withContext(dispatcher) {
+ trial.scenario.experiment.evaluator(trial)
+ }
+ } finally {
+ tickets.release()
+ }
+ }
+ }
+ }
+
+ override fun close(): Unit = dispatcher.close()
+
+ override fun toString(): String = "ThreadPoolScheduler"
+}
diff --git a/simulator/opendc-harness/src/main/kotlin/org/opendc/harness/engine/scheduler/ThreadPoolExperimentSchedulerProvider.kt b/simulator/opendc-harness/src/main/kotlin/org/opendc/harness/engine/scheduler/ThreadPoolExperimentSchedulerProvider.kt
new file mode 100644
index 00000000..cf9a132f
--- /dev/null
+++ b/simulator/opendc-harness/src/main/kotlin/org/opendc/harness/engine/scheduler/ThreadPoolExperimentSchedulerProvider.kt
@@ -0,0 +1,33 @@
+/*
+ * Copyright (c) 2021 AtLarge Research
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
+ * of this software and associated documentation files (the "Software"), to deal
+ * in the Software without restriction, including without limitation the rights
+ * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+ * copies of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included in all
+ * copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
+ * SOFTWARE.
+ */
+
+package org.opendc.harness.engine.scheduler
+
+/**
+ * An [ExperimentSchedulerProvider] for constructing a [ThreadPoolExperimentScheduler].
+ */
+public class ThreadPoolExperimentSchedulerProvider : ExperimentSchedulerProvider {
+ override val id: String = "thread-pool"
+
+ override fun create(): ExperimentScheduler =
+ ThreadPoolExperimentScheduler(Runtime.getRuntime().availableProcessors())
+}
diff --git a/simulator/opendc-harness/src/main/kotlin/org/opendc/harness/engine/strategy/CartesianExperimentStrategy.kt b/simulator/opendc-harness/src/main/kotlin/org/opendc/harness/engine/strategy/CartesianExperimentStrategy.kt
new file mode 100644
index 00000000..e5e08003
--- /dev/null
+++ b/simulator/opendc-harness/src/main/kotlin/org/opendc/harness/engine/strategy/CartesianExperimentStrategy.kt
@@ -0,0 +1,55 @@
+/*
+ * 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.harness.engine.strategy
+
+import org.opendc.harness.api.ExperimentDefinition
+import org.opendc.harness.api.Parameter
+import org.opendc.harness.api.Scenario
+import org.opendc.harness.internal.ScenarioImpl
+
+/**
+ * An [ExperimentStrategy] that takes the cartesian product of the parameters and evaluates every combination.
+ */
+public object CartesianExperimentStrategy : ExperimentStrategy {
+ /**
+ * Build the trials of an experiment.
+ */
+ override fun generate(experiment: ExperimentDefinition): Sequence<Scenario> {
+ return experiment.parameters
+ .asSequence()
+ .map { param -> mapParameter(param).map { value -> listOf(param to value) } }
+ .reduce { acc, param ->
+ acc.flatMap { x -> param.map { y -> x + y } }
+ }
+ .mapIndexed { id, values -> ScenarioImpl(id, experiment, values.toMap()) }
+ }
+
+ /**
+ * Instantiate a parameter and return a sequence of possible values.
+ */
+ private fun <T> mapParameter(param: Parameter<T>): Sequence<T> {
+ return when (param) {
+ is Parameter.Generic<T> -> param.values.asSequence()
+ }
+ }
+}
diff --git a/simulator/opendc-harness/src/main/kotlin/org/opendc/harness/engine/strategy/CartesianExperimentStrategyProvider.kt b/simulator/opendc-harness/src/main/kotlin/org/opendc/harness/engine/strategy/CartesianExperimentStrategyProvider.kt
new file mode 100644
index 00000000..f18795a3
--- /dev/null
+++ b/simulator/opendc-harness/src/main/kotlin/org/opendc/harness/engine/strategy/CartesianExperimentStrategyProvider.kt
@@ -0,0 +1,32 @@
+/*
+ * 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.harness.engine.strategy
+
+/**
+ * An [ExperimentStrategyProvider] for constructing a [CartesianExperimentStrategy].
+ */
+public class CartesianExperimentStrategyProvider : ExperimentStrategyProvider {
+ override val id: String = "cartesian"
+
+ override fun create(): ExperimentStrategy = CartesianExperimentStrategy
+}
diff --git a/simulator/opendc-harness/src/main/kotlin/org/opendc/harness/engine/strategy/ExperimentStrategy.kt b/simulator/opendc-harness/src/main/kotlin/org/opendc/harness/engine/strategy/ExperimentStrategy.kt
new file mode 100644
index 00000000..3a0148ad
--- /dev/null
+++ b/simulator/opendc-harness/src/main/kotlin/org/opendc/harness/engine/strategy/ExperimentStrategy.kt
@@ -0,0 +1,40 @@
+/*
+ * Copyright (c) 2021 AtLarge Research
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
+ * of this software and associated documentation files (the "Software"), to deal
+ * in the Software without restriction, including without limitation the rights
+ * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+ * copies of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included in all
+ * copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
+ * SOFTWARE.
+ */
+
+package org.opendc.harness.engine.strategy
+
+import org.opendc.harness.api.ExperimentDefinition
+import org.opendc.harness.api.Scenario
+
+/**
+ * The [ExperimentStrategy] is responsible for traversing the design space of an [ExperimentDefinition] based on its
+ * parameters, generating concrete points in the space represented as [Scenario]s.
+ */
+public interface ExperimentStrategy {
+ /**
+ * Generate the points in the design space of the specified [experiment] to explore.
+ *
+ * @param experiment The experiment design space to explore.
+ * @return A sequence of [Scenario]s which may be explored by the [ExperimentEngine].
+ */
+ public fun generate(experiment: ExperimentDefinition): Sequence<Scenario>
+}
diff --git a/simulator/opendc-harness/src/main/kotlin/org/opendc/harness/engine/strategy/ExperimentStrategyProvider.kt b/simulator/opendc-harness/src/main/kotlin/org/opendc/harness/engine/strategy/ExperimentStrategyProvider.kt
new file mode 100644
index 00000000..7fa05f34
--- /dev/null
+++ b/simulator/opendc-harness/src/main/kotlin/org/opendc/harness/engine/strategy/ExperimentStrategyProvider.kt
@@ -0,0 +1,57 @@
+/*
+ * 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.harness.engine.strategy
+
+import java.util.*
+
+/**
+ * A factory for constructing an [ExperimentStrategy].
+ */
+public interface ExperimentStrategyProvider {
+ /**
+ * A unique identifier for this strategy implementation.
+ *
+ * Each experiment strategy must provide a unique ID, so that they can be selected by the user.
+ * When in doubt, you may use the fully qualified name of your custom [ExperimentStrategy] implementation class.
+ */
+ public val id: String
+
+ /**
+ * Factory method for creating a new [ExperimentStrategy] instance.
+ */
+ public fun create(): ExperimentStrategy
+
+ public companion object {
+ /**
+ * The available [ExperimentStrategyProvider]s.
+ */
+ private val providers by lazy { ServiceLoader.load(ExperimentStrategyProvider::class.java) }
+
+ /**
+ * Obtain the [ExperimentStrategy] with the specified [id] or return `null`.
+ */
+ public fun findById(id: String): ExperimentStrategyProvider? {
+ return providers.find { it.id == id }
+ }
+ }
+}
diff --git a/simulator/opendc-harness/src/main/kotlin/org/opendc/harness/internal/CompositeDiscovery.kt b/simulator/opendc-harness/src/main/kotlin/org/opendc/harness/internal/CompositeDiscovery.kt
new file mode 100644
index 00000000..67a895e4
--- /dev/null
+++ b/simulator/opendc-harness/src/main/kotlin/org/opendc/harness/internal/CompositeDiscovery.kt
@@ -0,0 +1,47 @@
+/*
+ * 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.harness.internal
+
+import kotlinx.coroutines.FlowPreview
+import kotlinx.coroutines.flow.*
+import org.opendc.harness.api.ExperimentDefinition
+import org.opendc.harness.engine.discovery.Discovery
+import org.opendc.harness.engine.discovery.DiscoveryProvider
+import org.opendc.harness.engine.discovery.DiscoveryRequest
+
+/**
+ * A composite [Discovery] instance that combines the results of multiple delegate instances.
+ */
+internal class CompositeDiscovery(providers: Iterable<DiscoveryProvider>) : Discovery {
+ /**
+ * The [Discovery] instances to delegate to.
+ */
+ private val delegates = providers.map { it.create() }
+
+ @OptIn(FlowPreview::class)
+ override fun discover(request: DiscoveryRequest): Flow<ExperimentDefinition> {
+ return delegates.asFlow()
+ .map { it.discover(request) }
+ .flattenMerge(delegates.size)
+ }
+}
diff --git a/simulator/opendc-harness/src/main/kotlin/org/opendc/harness/internal/CompositeExperimentExecutionListener.kt b/simulator/opendc-harness/src/main/kotlin/org/opendc/harness/internal/CompositeExperimentExecutionListener.kt
new file mode 100644
index 00000000..a3cd6bd2
--- /dev/null
+++ b/simulator/opendc-harness/src/main/kotlin/org/opendc/harness/internal/CompositeExperimentExecutionListener.kt
@@ -0,0 +1,57 @@
+/*
+ * 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.harness.internal
+
+import org.opendc.harness.api.ExperimentDefinition
+import org.opendc.harness.api.Scenario
+import org.opendc.harness.api.Trial
+import org.opendc.harness.engine.ExperimentExecutionListener
+
+/**
+ * An [ExperimentExecutionListener] that composes multiple other listeners.
+ */
+public class CompositeExperimentExecutionListener(private val listeners: List<ExperimentExecutionListener>) : ExperimentExecutionListener {
+ override fun experimentStarted(experiment: ExperimentDefinition) {
+ listeners.forEach { it.experimentStarted(experiment) }
+ }
+
+ override fun experimentFinished(experiment: ExperimentDefinition, throwable: Throwable?) {
+ listeners.forEach { it.experimentFinished(experiment, throwable) }
+ }
+
+ override fun scenarioStarted(scenario: Scenario) {
+ listeners.forEach { it.scenarioStarted(scenario) }
+ }
+
+ override fun scenarioFinished(scenario: Scenario, throwable: Throwable?) {
+ listeners.forEach { it.scenarioFinished(scenario, throwable) }
+ }
+
+ override fun trialStarted(trial: Trial) {
+ listeners.forEach { it.trialStarted(trial) }
+ }
+
+ override fun trialFinished(trial: Trial, throwable: Throwable?) {
+ listeners.forEach { it.trialFinished(trial, throwable) }
+ }
+}
diff --git a/simulator/opendc-harness/src/main/kotlin/org/opendc/harness/internal/DslDiscovery.kt b/simulator/opendc-harness/src/main/kotlin/org/opendc/harness/internal/DslDiscovery.kt
new file mode 100644
index 00000000..eb6303d6
--- /dev/null
+++ b/simulator/opendc-harness/src/main/kotlin/org/opendc/harness/internal/DslDiscovery.kt
@@ -0,0 +1,101 @@
+/*
+ * Copyright (c) 2021 AtLarge Research
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
+ * of this software and associated documentation files (the "Software"), to deal
+ * in the Software without restriction, including without limitation the rights
+ * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+ * copies of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included in all
+ * copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
+ * SOFTWARE.
+ */
+
+package org.opendc.harness.internal
+
+import io.github.classgraph.ClassGraph
+import io.github.classgraph.ScanResult
+import kotlinx.coroutines.flow.Flow
+import kotlinx.coroutines.flow.asFlow
+import org.opendc.harness.api.ExperimentDefinition
+import org.opendc.harness.dsl.Experiment
+import org.opendc.harness.engine.discovery.Discovery
+import org.opendc.harness.engine.discovery.DiscoveryFilter
+import org.opendc.harness.engine.discovery.DiscoveryRequest
+import org.opendc.harness.engine.discovery.DiscoverySelector
+
+/**
+ * A [Discovery] implementation that discovers [Experiment] instances on the classpath.
+ */
+internal class DslDiscovery : Discovery {
+ /*
+ * Lazily memoize the results of the classpath scan.
+ */
+ private val scanResult by lazy { scan() }
+
+ override fun discover(request: DiscoveryRequest): Flow<ExperimentDefinition> {
+ return findExperiments()
+ .map { cls ->
+ val exp = cls.constructors[0].newInstance() as Experiment
+ exp.toDefinition()
+ }
+ .filter(select(request.selectors))
+ .filter(filter(request.filters))
+ .asFlow()
+ }
+
+ /**
+ * Find the classes on the classpath implementing the [Experiment] class.
+ */
+ private fun findExperiments(): Sequence<Class<out Experiment>> {
+ return scanResult
+ .getSubclasses(Experiment::class.java.name)
+ .filter { !(it.isAbstract || it.isInterface) }
+ .map { it.loadClass() }
+ .filterIsInstance<Class<out Experiment>>()
+ .asSequence()
+ }
+
+ /**
+ * Create a predicate for filtering the experiments based on the specified [filters].
+ */
+ private fun filter(filters: List<DiscoveryFilter>): (ExperimentDefinition) -> Boolean = { def ->
+ filters.isEmpty() || filters.all { it.test(def) }
+ }
+
+ /**
+ * Create a predicate for selecting the experiments based on the specified [selectors].
+ */
+ private fun select(selectors: List<DiscoverySelector>): (ExperimentDefinition) -> Boolean = { def ->
+ selectors.isEmpty() || selectors.any { it.test(def) }
+ }
+
+ /**
+ * Scan the classpath using [ClassGraph].
+ */
+ private fun scan(): ScanResult {
+ return ClassGraph()
+ .enableClassInfo()
+ .enableExternalClasses()
+ .ignoreClassVisibility()
+ .rejectPackages(
+ "java.*",
+ "javax.*",
+ "sun.*",
+ "com.sun.*",
+ "kotlin.*",
+ "androidx.*",
+ "org.jetbrains.kotlin.*",
+ "org.junit.*"
+ ).scan()
+ }
+}
diff --git a/simulator/opendc-harness/src/main/kotlin/org/opendc/harness/internal/DslDiscoveryProvider.kt b/simulator/opendc-harness/src/main/kotlin/org/opendc/harness/internal/DslDiscoveryProvider.kt
new file mode 100644
index 00000000..752ba4bb
--- /dev/null
+++ b/simulator/opendc-harness/src/main/kotlin/org/opendc/harness/internal/DslDiscoveryProvider.kt
@@ -0,0 +1,36 @@
+/*
+ * Copyright (c) 2021 AtLarge Research
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
+ * of this software and associated documentation files (the "Software"), to deal
+ * in the Software without restriction, including without limitation the rights
+ * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+ * copies of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included in all
+ * copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
+ * SOFTWARE.
+ */
+
+package org.opendc.harness.internal
+
+import org.opendc.harness.dsl.Experiment
+import org.opendc.harness.engine.discovery.Discovery
+import org.opendc.harness.engine.discovery.DiscoveryProvider
+
+/**
+ * A [DiscoveryProvider] for the [Experiment]s on the classpath.
+ */
+public class DslDiscoveryProvider : DiscoveryProvider {
+ override val id: String = "dsl"
+
+ override fun create(): Discovery = DslDiscovery()
+}
diff --git a/simulator/opendc-harness/src/main/kotlin/org/opendc/harness/internal/ParameterDelegate.kt b/simulator/opendc-harness/src/main/kotlin/org/opendc/harness/internal/ParameterDelegate.kt
new file mode 100644
index 00000000..aaf90b99
--- /dev/null
+++ b/simulator/opendc-harness/src/main/kotlin/org/opendc/harness/internal/ParameterDelegate.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.harness.internal
+
+import org.opendc.harness.api.Parameter
+import org.opendc.harness.dsl.Experiment
+import kotlin.properties.ReadOnlyProperty
+import kotlin.reflect.KProperty
+
+/**
+ * A delegate for an experiment parameter.
+ *
+ * @property parameter The parameter descriptor of this delegate.
+ */
+internal class ParameterDelegate<T>(val parameter: Parameter<T>) : ReadOnlyProperty<Experiment, T> {
+ /**
+ * Obtain the value for the parameter.
+ */
+ override fun getValue(thisRef: Experiment, property: KProperty<*>): T {
+ val scenario = thisRef.scenario ?: throw IllegalStateException("Cannot use parameters before activation")
+ return scenario[parameter]
+ }
+}
diff --git a/simulator/opendc-workflows/src/main/kotlin/org/opendc/workflows/service/stage/resource/RandomResourceSelectionPolicy.kt b/simulator/opendc-harness/src/main/kotlin/org/opendc/harness/internal/ScenarioImpl.kt
index caf87c70..d255004d 100644
--- a/simulator/opendc-workflows/src/main/kotlin/org/opendc/workflows/service/stage/resource/RandomResourceSelectionPolicy.kt
+++ b/simulator/opendc-harness/src/main/kotlin/org/opendc/harness/internal/ScenarioImpl.kt
@@ -1,5 +1,5 @@
/*
- * Copyright (c) 2020 AtLarge Research
+ * Copyright (c) 2021 AtLarge Research
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to deal
@@ -20,26 +20,30 @@
* SOFTWARE.
*/
-package org.opendc.workflows.service.stage.resource
+package org.opendc.harness.internal
-import org.opendc.compute.core.metal.Node
-import org.opendc.workflows.service.StageWorkflowService
-import java.util.*
+import org.opendc.harness.api.ExperimentDefinition
+import org.opendc.harness.api.Parameter
+import org.opendc.harness.api.Scenario
/**
- * A [ResourceSelectionPolicy] that randomly orders the machines.
+ * Internal implementation of a [Scenario].
*/
-public object RandomResourceSelectionPolicy : ResourceSelectionPolicy {
- override fun invoke(scheduler: StageWorkflowService): Comparator<Node> = object : Comparator<Node> {
- private val ids: Map<Node, Long>
+internal data class ScenarioImpl(
+ override val id: Int,
+ override val experiment: ExperimentDefinition,
+ val parameters: Map<Parameter<*>, Any?>
+) : Scenario {
- init {
- val random = Random(123)
- ids = scheduler.nodes.associateWith { random.nextLong() }
+ override fun <T> get(param: Parameter<T>): T {
+ if (!parameters.containsKey(param)) {
+ throw IllegalArgumentException("Unknown parameter for this scenario.")
}
- override fun compare(o1: Node, o2: Node): Int = compareValuesBy(o1, o2) { ids[it] }
+ // This cast should always succeed
+ @Suppress("UNCHECKED_CAST")
+ return parameters[param] as T
}
- override fun toString(): String = "Random"
+ override fun toString(): String = "Scenario"
}
diff --git a/simulator/opendc-experiments/opendc-experiments-sc20/src/main/kotlin/org/opendc/experiments/sc20/reporter/ConsoleExperimentReporter.kt b/simulator/opendc-harness/src/main/kotlin/org/opendc/harness/runner/console/ConsoleExperimentReporter.kt
index af61622a..2db74ef4 100644
--- a/simulator/opendc-experiments/opendc-experiments-sc20/src/main/kotlin/org/opendc/experiments/sc20/reporter/ConsoleExperimentReporter.kt
+++ b/simulator/opendc-harness/src/main/kotlin/org/opendc/harness/runner/console/ConsoleExperimentReporter.kt
@@ -1,5 +1,5 @@
/*
- * Copyright (c) 2020 AtLarge Research
+ * Copyright (c) 2021 AtLarge Research
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to deal
@@ -20,24 +20,22 @@
* SOFTWARE.
*/
-package org.opendc.experiments.sc20.reporter
+package org.opendc.harness.runner.console
import me.tongfei.progressbar.ProgressBar
import me.tongfei.progressbar.ProgressBarBuilder
import mu.KotlinLogging
-import org.opendc.experiments.sc20.experiment.Run
-import org.opendc.experiments.sc20.runner.ExperimentDescriptor
-import org.opendc.experiments.sc20.runner.execution.ExperimentExecutionListener
-import org.opendc.experiments.sc20.runner.execution.ExperimentExecutionResult
+import org.opendc.harness.api.Trial
+import org.opendc.harness.engine.ExperimentExecutionListener
/**
* A reporter that reports the experiment progress to the console.
*/
public class ConsoleExperimentReporter : ExperimentExecutionListener, AutoCloseable {
/**
- * The active [Run]s.
+ * The active [Trial]s.
*/
- private val runs: MutableSet<Run> = mutableSetOf()
+ private val trials: MutableSet<Trial> = mutableSetOf()
/**
* The total number of runs.
@@ -57,29 +55,23 @@ public class ConsoleExperimentReporter : ExperimentExecutionListener, AutoClosea
.setInitialMax(1)
.build()
- override fun descriptorRegistered(descriptor: ExperimentDescriptor) {
- if (descriptor is Run) {
- runs += descriptor
- pb.maxHint((++total).toLong())
- }
- }
+ override fun trialFinished(trial: Trial, throwable: Throwable?) {
+ trials -= trial
- override fun executionFinished(descriptor: ExperimentDescriptor, result: ExperimentExecutionResult) {
- if (descriptor is Run) {
- runs -= descriptor
-
- pb.stepTo(total - runs.size.toLong())
- if (runs.isEmpty()) {
- pb.close()
- }
+ pb.stepTo(total - trials.size.toLong())
+ if (trials.isEmpty()) {
+ pb.close()
}
- if (result is ExperimentExecutionResult.Failed) {
- logger.warn(result.throwable) { "Descriptor $descriptor failed" }
+ if (throwable != null) {
+ logger.warn(throwable) { "Trial $trial failed" }
}
}
- override fun executionStarted(descriptor: ExperimentDescriptor) {}
+ override fun trialStarted(trial: Trial) {
+ trials += trial
+ pb.maxHint((++total).toLong())
+ }
override fun close() {
pb.close()
diff --git a/simulator/opendc-harness/src/main/kotlin/org/opendc/harness/runner/console/ConsoleRunner.kt b/simulator/opendc-harness/src/main/kotlin/org/opendc/harness/runner/console/ConsoleRunner.kt
new file mode 100644
index 00000000..ae221c7f
--- /dev/null
+++ b/simulator/opendc-harness/src/main/kotlin/org/opendc/harness/runner/console/ConsoleRunner.kt
@@ -0,0 +1,99 @@
+/*
+ * 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.harness.runner.console
+
+import com.github.ajalt.clikt.core.CliktCommand
+import com.github.ajalt.clikt.parameters.options.default
+import com.github.ajalt.clikt.parameters.options.multiple
+import com.github.ajalt.clikt.parameters.options.option
+import com.github.ajalt.clikt.parameters.types.int
+import mu.KotlinLogging
+import org.opendc.harness.engine.ExperimentEngineLauncher
+import org.opendc.harness.engine.discovery.DiscoveryProvider
+import org.opendc.harness.engine.discovery.DiscoveryRequest
+import org.opendc.harness.engine.discovery.DiscoverySelector
+import org.opendc.harness.engine.scheduler.ThreadPoolExperimentScheduler
+
+/**
+ * The logger for this experiment runner.
+ */
+private val logger = KotlinLogging.logger {}
+
+/**
+ * The command line interface for the console experiment runner.
+ */
+public class ConsoleRunner : CliktCommand(name = "opendc-harness") {
+ /**
+ * The number of repeats per scenario.
+ */
+ private val repeats by option("-r", "--repeats", help = "Number of repeats per scenario")
+ .int()
+ .default(1)
+
+ /**
+ * The selected experiments to run by name.
+ */
+ private val experiments by option("-e", "--experiments", help = "Names of experiments to explore")
+ .multiple(emptyList())
+
+ /**
+ * The maximum number of worker threads to use.
+ */
+ private val parallelism by option("-p", "--parallelism", help = "Maximum number of concurrent simulation runs")
+ .int()
+ .default(Runtime.getRuntime().availableProcessors())
+
+ override fun run() {
+ logger.info { "Starting OpenDC Console Experiment Runner" }
+
+ val discovery = DiscoveryProvider.createComposite()
+ val experiments = discovery.discover(
+ DiscoveryRequest(
+ selectors = experiments.map { DiscoverySelector.Name(it) }
+ )
+ )
+
+ val reporter = ConsoleExperimentReporter()
+ val scheduler = ThreadPoolExperimentScheduler(parallelism)
+
+ try {
+ ExperimentEngineLauncher()
+ .withListener(reporter)
+ .withRepeats(repeats)
+ .withScheduler(scheduler)
+ .runBlocking(experiments)
+ } catch (e: Throwable) {
+ logger.error(e) { "Failed to finish experiments" }
+ } finally {
+ reporter.close()
+ scheduler.close()
+ }
+
+ logger.info { "Finished all experiments. Exiting." }
+ }
+}
+
+/**
+ * Main entry point of the experiment runner.
+ */
+public fun main(args: Array<String>): Unit = ConsoleRunner().main(args)
diff --git a/simulator/opendc-harness/src/main/kotlin/org/opendc/harness/runner/junit5/JUnitExperimentExecutionListener.kt b/simulator/opendc-harness/src/main/kotlin/org/opendc/harness/runner/junit5/JUnitExperimentExecutionListener.kt
new file mode 100644
index 00000000..58791549
--- /dev/null
+++ b/simulator/opendc-harness/src/main/kotlin/org/opendc/harness/runner/junit5/JUnitExperimentExecutionListener.kt
@@ -0,0 +1,152 @@
+/*
+ * 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.harness.runner.junit5
+
+import org.junit.platform.engine.*
+import org.junit.platform.engine.support.descriptor.AbstractTestDescriptor
+import org.junit.platform.engine.support.descriptor.EngineDescriptor
+import org.opendc.harness.api.ExperimentDefinition
+import org.opendc.harness.api.Scenario
+import org.opendc.harness.api.Trial
+import org.opendc.harness.engine.ExperimentExecutionListener
+
+/**
+ * An [ExperimentExecutionListener] that notifies JUnit platform of the progress of the experiment trials.
+ */
+public class JUnitExperimentExecutionListener(
+ private val listener: EngineExecutionListener,
+ private val root: EngineDescriptor
+) : ExperimentExecutionListener {
+ /**
+ * The current active experiments.
+ */
+ private val experiments = mutableMapOf<ExperimentDefinition, TestDescriptor>()
+
+ /**
+ * The current active scenarios.
+ */
+ private val scenarios = mutableMapOf<Scenario, TestDescriptor>()
+
+ /**
+ * The current active trials.
+ */
+ private val trials = mutableMapOf<Trial, TestDescriptor>()
+
+ override fun experimentStarted(experiment: ExperimentDefinition) {
+ val descriptor = experiment.toDescriptor(root)
+ root.addChild(descriptor)
+ experiments[experiment] = descriptor
+
+ listener.dynamicTestRegistered(descriptor)
+ listener.executionStarted(descriptor)
+ }
+
+ override fun experimentFinished(experiment: ExperimentDefinition, throwable: Throwable?) {
+ val descriptor = experiments.remove(experiment)
+
+ if (throwable != null) {
+ listener.executionFinished(descriptor, TestExecutionResult.failed(throwable))
+ } else {
+ listener.executionFinished(descriptor, TestExecutionResult.successful())
+ }
+ }
+
+ override fun scenarioStarted(scenario: Scenario) {
+ val parent = experiments[scenario.experiment] ?: return
+ val descriptor = scenario.toDescriptor(parent)
+ parent.addChild(descriptor)
+ scenarios[scenario] = descriptor
+
+ listener.dynamicTestRegistered(descriptor)
+ listener.executionStarted(descriptor)
+ }
+
+ override fun scenarioFinished(scenario: Scenario, throwable: Throwable?) {
+ val descriptor = scenarios.remove(scenario)
+
+ if (throwable != null) {
+ listener.executionFinished(descriptor, TestExecutionResult.failed(throwable))
+ } else {
+ listener.executionFinished(descriptor, TestExecutionResult.successful())
+ }
+ }
+
+ override fun trialStarted(trial: Trial) {
+ val parent = scenarios[trial.scenario] ?: return
+ val descriptor = trial.toDescriptor(parent)
+ parent.addChild(descriptor)
+ trials[trial] = descriptor
+
+ listener.dynamicTestRegistered(descriptor)
+ listener.executionStarted(descriptor)
+ }
+
+ override fun trialFinished(trial: Trial, throwable: Throwable?) {
+ val descriptor = trials.remove(trial)
+
+ if (throwable != null) {
+ listener.executionFinished(descriptor, TestExecutionResult.failed(throwable))
+ } else {
+ listener.executionFinished(descriptor, TestExecutionResult.successful())
+ }
+ }
+
+ /**
+ * Create a [TestDescriptor] for an [ExperimentDefinition].
+ */
+ private fun ExperimentDefinition.toDescriptor(parent: TestDescriptor): TestDescriptor {
+ return object : AbstractTestDescriptor(parent.uniqueId.append("experiment", name), name) {
+ override fun getType(): TestDescriptor.Type = TestDescriptor.Type.CONTAINER
+
+ override fun mayRegisterTests(): Boolean = true
+
+ override fun toString(): String = "ExperimentDescriptor"
+ }
+ }
+
+ /**
+ * Create a [TestDescriptor] for a [Scenario].
+ */
+ private fun Scenario.toDescriptor(parent: TestDescriptor): TestDescriptor {
+ return object : AbstractTestDescriptor(parent.uniqueId.append("scenario", id.toString()), "Scenario $id") {
+ override fun getType(): TestDescriptor.Type = TestDescriptor.Type.CONTAINER_AND_TEST
+
+ override fun mayRegisterTests(): Boolean = true
+
+ override fun toString(): String = "ScenarioDescriptor"
+ }
+ }
+
+ /**
+ * Create a [TestDescriptor] for a [Trial].
+ */
+ private fun Trial.toDescriptor(parent: TestDescriptor): TestDescriptor {
+ return object : AbstractTestDescriptor(parent.uniqueId.append("repeat", repeat.toString()), "Repeat $repeat") {
+ override fun getType(): TestDescriptor.Type = TestDescriptor.Type.TEST
+
+ override fun mayRegisterTests(): Boolean = false
+
+ override fun toString(): String = "TrialDescriptor"
+ }
+ }
+}
diff --git a/simulator/opendc-harness/src/main/kotlin/org/opendc/harness/runner/junit5/OpenDCTestEngine.kt b/simulator/opendc-harness/src/main/kotlin/org/opendc/harness/runner/junit5/OpenDCTestEngine.kt
new file mode 100644
index 00000000..685cd41a
--- /dev/null
+++ b/simulator/opendc-harness/src/main/kotlin/org/opendc/harness/runner/junit5/OpenDCTestEngine.kt
@@ -0,0 +1,94 @@
+/*
+ * 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.harness.runner.junit5
+
+import kotlinx.coroutines.flow.Flow
+import kotlinx.coroutines.flow.emptyFlow
+import mu.KotlinLogging
+import org.junit.platform.engine.*
+import org.junit.platform.engine.discovery.ClassNameFilter
+import org.junit.platform.engine.discovery.ClassSelector
+import org.junit.platform.engine.discovery.MethodSelector
+import org.junit.platform.engine.support.descriptor.EngineDescriptor
+import org.junit.platform.launcher.LauncherDiscoveryRequest
+import org.opendc.harness.api.ExperimentDefinition
+import org.opendc.harness.engine.ExperimentEngineLauncher
+import org.opendc.harness.engine.discovery.DiscoveryFilter
+import org.opendc.harness.engine.discovery.DiscoveryProvider
+import org.opendc.harness.engine.discovery.DiscoveryRequest
+import org.opendc.harness.engine.discovery.DiscoverySelector
+
+/**
+ * A [TestEngine] implementation that is able to run experiments defined using the harness.
+ */
+public class OpenDCTestEngine : TestEngine {
+ /**
+ * The logging instance for this engine.
+ */
+ private val logger = KotlinLogging.logger {}
+
+ override fun getId(): String = "opendc"
+
+ override fun discover(request: EngineDiscoveryRequest, uniqueId: UniqueId): TestDescriptor {
+ // Test whether are excluded from the engines
+ val isEnabled = (request as? LauncherDiscoveryRequest)?.engineFilters?.all { it.toPredicate().test(this) } ?: true
+ if (!isEnabled) {
+ return ExperimentEngineDescriptor(uniqueId, emptyFlow())
+ }
+
+ // IntelliJ will pass a [MethodSelector] to run just a single method inside a file. In that
+ // case, no experiments should be discovered, since we support only experiments by class.
+ if (request.getSelectorsByType(MethodSelector::class.java).isNotEmpty()) {
+ return ExperimentEngineDescriptor(uniqueId, emptyFlow())
+ }
+
+ val classNames = request.getSelectorsByType(ClassSelector::class.java).map { DiscoverySelector.Meta("class.name", it.className) }
+ val classNameFilters = request.getFiltersByType(ClassNameFilter::class.java).map { DiscoveryFilter.Name(it.toPredicate()) }
+
+ val discovery = DiscoveryProvider.createComposite()
+ val definitions = discovery.discover(DiscoveryRequest(classNames, classNameFilters))
+
+ return ExperimentEngineDescriptor(uniqueId, definitions)
+ }
+
+ override fun execute(request: ExecutionRequest) {
+ logger.debug { "JUnit ExecutionRequest[${request::class.java.name}] [configurationParameters=${request.configurationParameters}; rootTestDescriptor=${request.rootTestDescriptor}]" }
+ val root = request.rootTestDescriptor as ExperimentEngineDescriptor
+ val listener = request.engineExecutionListener
+
+ listener.executionStarted(root)
+
+ try {
+ ExperimentEngineLauncher()
+ .withListener(JUnitExperimentExecutionListener(listener, root))
+ .runBlocking(root.experiments)
+ listener.executionFinished(root, TestExecutionResult.successful())
+ } catch (e: Throwable) {
+ listener.executionFinished(root, TestExecutionResult.failed(e))
+ }
+ }
+
+ private class ExperimentEngineDescriptor(id: UniqueId, val experiments: Flow<ExperimentDefinition>) : EngineDescriptor(id, "opendc") {
+ override fun mayRegisterTests(): Boolean = true
+ }
+}
diff --git a/simulator/opendc-harness/src/main/resources/META-INF/services/org.junit.platform.engine.TestEngine b/simulator/opendc-harness/src/main/resources/META-INF/services/org.junit.platform.engine.TestEngine
new file mode 100644
index 00000000..b83eec0c
--- /dev/null
+++ b/simulator/opendc-harness/src/main/resources/META-INF/services/org.junit.platform.engine.TestEngine
@@ -0,0 +1 @@
+org.opendc.harness.runner.junit5.OpenDCTestEngine
diff --git a/simulator/opendc-harness/src/main/resources/META-INF/services/org.opendc.harness.engine.discovery.DiscoveryProvider b/simulator/opendc-harness/src/main/resources/META-INF/services/org.opendc.harness.engine.discovery.DiscoveryProvider
new file mode 100644
index 00000000..d6a73ded
--- /dev/null
+++ b/simulator/opendc-harness/src/main/resources/META-INF/services/org.opendc.harness.engine.discovery.DiscoveryProvider
@@ -0,0 +1 @@
+org.opendc.harness.internal.DslDiscoveryProvider
diff --git a/simulator/opendc-harness/src/main/resources/META-INF/services/org.opendc.harness.engine.scheduler.ExperimentSchedulerProvider b/simulator/opendc-harness/src/main/resources/META-INF/services/org.opendc.harness.engine.scheduler.ExperimentSchedulerProvider
new file mode 100644
index 00000000..2ba3a7cb
--- /dev/null
+++ b/simulator/opendc-harness/src/main/resources/META-INF/services/org.opendc.harness.engine.scheduler.ExperimentSchedulerProvider
@@ -0,0 +1 @@
+org.opendc.harness.engine.scheduler.ThreadPoolExperimentSchedulerProvider
diff --git a/simulator/opendc-harness/src/main/resources/META-INF/services/org.opendc.harness.engine.strategy.ExperimentStrategyProvider b/simulator/opendc-harness/src/main/resources/META-INF/services/org.opendc.harness.engine.strategy.ExperimentStrategyProvider
new file mode 100644
index 00000000..cb1c70ac
--- /dev/null
+++ b/simulator/opendc-harness/src/main/resources/META-INF/services/org.opendc.harness.engine.strategy.ExperimentStrategyProvider
@@ -0,0 +1 @@
+org.opendc.harness.engine.strategy.CartesianExperimentStrategyProvider
diff --git a/simulator/opendc-harness/src/main/resources/log4j2.xml b/simulator/opendc-harness/src/main/resources/log4j2.xml
new file mode 100644
index 00000000..9553d964
--- /dev/null
+++ b/simulator/opendc-harness/src/main/resources/log4j2.xml
@@ -0,0 +1,40 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!--
+ ~ MIT License
+ ~
+ ~ Copyright (c) 2020 atlarge-research
+ ~
+ ~ Permission is hereby granted, free of charge, to any person obtaining a copy
+ ~ of this software and associated documentation files (the "Software"), to deal
+ ~ in the Software without restriction, including without limitation the rights
+ ~ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+ ~ copies of the Software, and to permit persons to whom the Software is
+ ~ furnished to do so, subject to the following conditions:
+ ~
+ ~ The above copyright notice and this permission notice shall be included in all
+ ~ copies or substantial portions of the Software.
+ ~
+ ~ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ ~ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ ~ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+ ~ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ ~ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+ ~ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
+ ~ SOFTWARE.
+ -->
+
+<Configuration status="WARN">
+ <Appenders>
+ <Console name="Console" target="SYSTEM_OUT">
+ <PatternLayout pattern="%d{HH:mm:ss.SSS} [%highlight{%-5level}] %logger{36} - %msg%n" disableAnsi="false"/>
+ </Console>
+ </Appenders>
+ <Loggers>
+ <Logger name="org.opendc" level="info" additivity="false">
+ <AppenderRef ref="Console"/>
+ </Logger>
+ <Root level="error">
+ <AppenderRef ref="Console"/>
+ </Root>
+ </Loggers>
+</Configuration>
diff --git a/simulator/opendc-harness/src/test/kotlin/org/opendc/harness/EngineTest.kt b/simulator/opendc-harness/src/test/kotlin/org/opendc/harness/EngineTest.kt
new file mode 100644
index 00000000..6f2989db
--- /dev/null
+++ b/simulator/opendc-harness/src/test/kotlin/org/opendc/harness/EngineTest.kt
@@ -0,0 +1,61 @@
+/*
+ * 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.harness
+
+import kotlinx.coroutines.flow.flowOf
+import kotlinx.coroutines.flow.toList
+import kotlinx.coroutines.runBlocking
+import org.junit.jupiter.api.Assertions.assertEquals
+import org.junit.jupiter.api.Assertions.assertNotNull
+import org.junit.jupiter.api.Test
+import org.opendc.harness.api.ExperimentDefinition
+import org.opendc.harness.engine.ExperimentEngine
+import org.opendc.harness.engine.ExperimentEngineLauncher
+import org.opendc.harness.engine.ExperimentExecutionListener
+import org.opendc.harness.engine.discovery.DiscoveryProvider
+import org.opendc.harness.engine.discovery.DiscoveryRequest
+
+/**
+ * A test suite for the [ExperimentEngine].
+ */
+internal class EngineTest {
+ @Test
+ fun test() {
+ val listener = object : ExperimentExecutionListener {}
+ ExperimentEngineLauncher()
+ .withListener(listener)
+ .runBlocking(flowOf(TestExperiment().toDefinition()))
+ }
+
+ @Test
+ fun discovery() {
+ runBlocking {
+ val discovery = DiscoveryProvider.findById("dsl")?.create()
+ assertNotNull(discovery)
+ val res = mutableListOf<ExperimentDefinition>()
+ discovery?.discover(DiscoveryRequest())?.toList(res)
+ println(res)
+ assertEquals(1, res.size)
+ }
+ }
+}
diff --git a/simulator/opendc-harness/src/test/kotlin/org/opendc/harness/TestExperiment.kt b/simulator/opendc-harness/src/test/kotlin/org/opendc/harness/TestExperiment.kt
new file mode 100644
index 00000000..bedd1c76
--- /dev/null
+++ b/simulator/opendc-harness/src/test/kotlin/org/opendc/harness/TestExperiment.kt
@@ -0,0 +1,54 @@
+/*
+ * 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.harness
+
+import org.opendc.harness.dsl.Experiment
+import org.opendc.harness.dsl.anyOf
+
+/**
+ * An experiment to test the harness' functionality.
+ */
+class TestExperiment : Experiment("Design Space Exploration") {
+ /**
+ * The cloud environment to use.
+ */
+ private val environment: String by anyOf(
+ "../../traces/setup-small.json",
+ "../../traces/setup.json",
+ "../../traces/setup-large.json"
+ )
+
+ /**
+ * The trace to use.
+ */
+ private val trace: String by anyOf(
+ "../../traces/gwf/askalon_workload_olde.gwf",
+ "../../traces/gwf/askalon_workload_ee.gwf",
+ "../../traces/gwf/chronos_exp_noscaler_ca.gwf"
+ )
+
+ override fun doRun(repeat: Int) {
+ println("Id $id, Run $repeat, Environment $environment, Trace $trace")
+ Thread.sleep(500 * id.toLong())
+ }
+}
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 2117b675..7796019a 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,6 +50,7 @@ import org.opendc.experiments.sc20.trace.Sc20ParquetTraceReader
import org.opendc.experiments.sc20.trace.Sc20RawParquetTraceReader
import org.opendc.format.trace.sc20.Sc20PerformanceInterferenceReader
import org.opendc.simulator.utils.DelayControllerClockAdapter
+import org.opendc.trace.core.EventTracer
import java.io.File
import kotlin.coroutines.coroutineContext
import kotlin.random.Random
@@ -238,13 +239,15 @@ public class RunnerCli : CliktCommand(name = "runner") {
val topologyId = scenario.getEmbedded(listOf("topology", "topologyId"), ObjectId::class.java)
val environment = TopologyParser(topologies, topologyId)
val monitor = WebExperimentMonitor()
+ val tracer = EventTracer(clock)
testScope.launch {
val (bareMetalProvisioner, scheduler) = createProvisioner(
this,
clock,
environment,
- allocationPolicy
+ allocationPolicy,
+ tracer
)
val failureDomain = if (operational.getBoolean("failuresEnabled")) {
diff --git a/simulator/opendc-simulator/opendc-simulator-compute/build.gradle.kts b/simulator/opendc-simulator/opendc-simulator-compute/build.gradle.kts
index cd7e5706..844a7c6d 100644
--- a/simulator/opendc-simulator/opendc-simulator-compute/build.gradle.kts
+++ b/simulator/opendc-simulator/opendc-simulator-compute/build.gradle.kts
@@ -28,6 +28,7 @@ plugins {
dependencies {
api(project(":opendc-simulator:opendc-simulator-core"))
+ implementation(project(":opendc-utils"))
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-simulator/opendc-simulator-compute/src/main/kotlin/org/opendc/simulator/compute/SimBareMetalMachine.kt b/simulator/opendc-simulator/opendc-simulator-compute/src/main/kotlin/org/opendc/simulator/compute/SimBareMetalMachine.kt
index c6d5bdd1..812b5f20 100644
--- a/simulator/opendc-simulator/opendc-simulator-compute/src/main/kotlin/org/opendc/simulator/compute/SimBareMetalMachine.kt
+++ b/simulator/opendc-simulator/opendc-simulator-compute/src/main/kotlin/org/opendc/simulator/compute/SimBareMetalMachine.kt
@@ -25,13 +25,13 @@ 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.model.ProcessingUnit
+import org.opendc.simulator.compute.workload.SimResourceCommand
import org.opendc.simulator.compute.workload.SimWorkload
-import java.lang.Runnable
+import org.opendc.utils.TimerScheduler
import java.time.Clock
-import kotlin.coroutines.ContinuationInterceptor
+import java.util.*
+import kotlin.coroutines.*
import kotlin.math.ceil
import kotlin.math.max
import kotlin.math.min
@@ -59,23 +59,29 @@ public class SimBareMetalMachine(
get() = usageState
/**
+ * A flag to indicate that the machine is terminated.
+ */
+ private var isTerminated = false
+
+ /**
+ * The [MutableStateFlow] containing the load of the server.
+ */
+ private val usageState = MutableStateFlow(0.0)
+
+ /**
* The current active workload.
*/
- private var activeWorkload: SimWorkload? = null
+ private var cont: Continuation<Unit>? = null
/**
- * Run the specified [SimWorkload] on this machine and suspend execution util the workload has finished.
+ * The active CPUs of this machine.
*/
- override suspend fun run(workload: SimWorkload) {
- require(activeWorkload == null) { "Run should not be called concurrently" }
+ private var cpus: List<Cpu> = emptyList()
- try {
- activeWorkload = workload
- workload.run(ctx)
- } finally {
- activeWorkload = null
- }
- }
+ /**
+ * The [TimerScheduler] to use for scheduling the interrupts.
+ */
+ private val scheduler = TimerScheduler<Cpu>(coroutineScope, clock)
/**
* The execution context in which the workload runs.
@@ -87,195 +93,220 @@ public class SimBareMetalMachine(
override val clock: Clock
get() = this@SimBareMetalMachine.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()
- }
+ override fun interrupt(cpu: Int) {
+ require(cpu < cpus.size) { "Invalid CPU identifier" }
+ cpus[cpu].interrupt()
+ }
+ }
- // Schedule the first work
- if (queue.hasNext()) {
- schedule(queue.next())
+ /**
+ * Run the specified [SimWorkload] on this machine and suspend execution util the workload has finished.
+ */
+ override suspend fun run(workload: SimWorkload) {
+ require(!isTerminated) { "Machine is terminated" }
+ require(cont == null) { "Run should not be called concurrently" }
- // A DisposableHandle to flush the work in case the call is cancelled
- val disposable = DisposableHandle {
- val end = clock.millis()
- val duration = end - start
+ workload.onStart(ctx)
- currentWork?.stop(duration)
- currentDisposable?.dispose()
+ return suspendCancellableCoroutine { cont ->
+ this.cont = cont
+ this.cpus = model.cpus.map { Cpu(it, workload) }
- // 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)
- }
- }
+ for (cpu in cpus) {
+ cpu.start()
}
}
}
/**
- * The [MutableStateFlow] containing the load of the server.
+ * Terminate the specified bare-metal machine.
*/
- private val usageState = MutableStateFlow(0.0)
+ override fun close() {
+ isTerminated = true
+ }
+
+ /**
+ * Update the usage of the machine.
+ */
+ private fun updateUsage() {
+ usageState.value = cpus.sumByDouble { it.speed } / cpus.sumByDouble { it.model.frequency }
+ }
/**
- * A disposable to prevent resetting the usage state for subsequent calls to onRun.
+ * This method is invoked when one of the CPUs has exited.
*/
- private var usageFlush: DisposableHandle? = null
+ private fun onCpuExit(cpu: Int) {
+ // Check whether all other CPUs have finished
+ if (cpus.all { it.hasExited }) {
+ val cont = cont
+ this.cont = null
+ cont?.resume(Unit)
+ }
+ }
/**
- * 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.
+ * This method is invoked when one of the CPUs failed.
*/
- @OptIn(InternalCoroutinesApi::class)
- private val delay = coroutineScope.coroutineContext[ContinuationInterceptor] as Delay
+ private fun onCpuFailure(e: Throwable) {
+ // Make sure no other tasks will be resumed.
+ scheduler.cancelAll()
+
+ // In case the flush fails with an exception, immediately propagate to caller, cancelling all other
+ // tasks.
+ val cont = cont
+ this.cont = null
+ cont?.resumeWithException(e)
+ }
/**
- * A slice to be processed.
+ * A physical CPU of the machine.
*/
- private inner class SliceWork(val slice: SimExecutionContext.Slice) {
+ private inner class Cpu(val model: ProcessingUnit, val workload: SimWorkload) {
/**
- * The duration after which the first processor finishes processing this slice.
+ * The current command.
*/
- val minExit: Long
+ private var currentCommand: CommandWrapper? = null
/**
- * The duration after which the last processor finishes processing this slice.
+ * The actual processing speed.
*/
- val maxExit: Long
+ var speed: Double = 0.0
+ set(value) {
+ field = value
+ updateUsage()
+ }
/**
- * A flag to indicate that the slice will exceed the deadline.
+ * A flag to indicate that the CPU is currently processing a command.
*/
- val exceedsDeadline: Boolean
- get() = slice.deadline < maxExit
+ var isIntermediate: Boolean = false
/**
- * The total amount of CPU usage.
+ * A flag to indicate that the CPU has exited.
*/
- val totalUsage: Double
+ var hasExited: Boolean = false
/**
- * A flag to indicate that this slice is empty.
+ * Process the specified [SimResourceCommand] for this CPU.
*/
- 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
+ fun process(command: SimResourceCommand) {
+ val timestamp = clock.millis()
+
+ val task = when (command) {
+ is SimResourceCommand.Idle -> {
+ speed = 0.0
+
+ val deadline = command.deadline
+
+ require(deadline >= timestamp) { "Deadline already passed" }
+
+ if (deadline != Long.MAX_VALUE) {
+ scheduler.startSingleTimerTo(this, deadline) { flush() }
+ } else {
+ null
+ }
+ }
+ is SimResourceCommand.Consume -> {
+ val work = command.work
+ val limit = command.limit
+ val deadline = command.deadline
+
+ require(deadline >= timestamp) { "Deadline already passed" }
+
+ speed = min(model.frequency, limit)
+
+ // The required duration to process all the work
+ val finishedAt = timestamp + ceil(work / speed * 1000).toLong()
+
+ scheduler.startSingleTimerTo(this, min(finishedAt, deadline)) { flush() }
+ }
+ is SimResourceCommand.Exit -> {
+ speed = 0.0
+ hasExited = true
+
+ onCpuExit(model.id)
+
+ null
}
}
- this.isEmpty = !nonEmpty
- this.totalUsage = totalUsage
- this.minExit = minExit
- this.maxExit = maxExit
+ assert(currentCommand == null) { "Concurrent access to current command" }
+ currentCommand = CommandWrapper(timestamp, command)
}
/**
- * Indicate that the work on the slice has started.
+ * Request the workload for more work.
*/
- fun start() {
- usageState.value = totalUsage / model.cpus.size
+ private fun next(remainingWork: Double) {
+ process(workload.onNext(ctx, model.id, remainingWork))
}
/**
- * Flush the work performed on the slice.
+ * Start the CPU.
*/
- fun stop(duration: Long): Boolean {
- var hasFinished = true
+ fun start() {
+ try {
+ isIntermediate = true
+
+ process(workload.onStart(ctx, model.id))
+ } catch (e: Throwable) {
+ onCpuFailure(e)
+ } finally {
+ isIntermediate = false
+ }
+ }
- 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
+ /**
+ * Flush the work performed by the CPU.
+ */
+ fun flush() {
+ try {
+ val (timestamp, command) = currentCommand ?: return
+
+ isIntermediate = true
+ currentCommand = null
+
+ // Cancel the running task and flush the progress
+ scheduler.cancel(this)
+
+ when (command) {
+ is SimResourceCommand.Idle -> next(remainingWork = 0.0)
+ is SimResourceCommand.Consume -> {
+ val duration = clock.millis() - timestamp
+ val remainingWork = if (duration > 0L) {
+ val processed = duration / 1000.0 * speed
+ max(0.0, command.work - processed)
+ } else {
+ 0.0
+ }
- if (res != 0L) {
- hasFinished = false
+ next(remainingWork)
+ }
+ SimResourceCommand.Exit -> throw IllegalStateException()
}
+ } catch (e: Throwable) {
+ onCpuFailure(e)
+ } finally {
+ isIntermediate = false
+ }
+ }
+
+ /**
+ * Interrupt the CPU.
+ */
+ fun interrupt() {
+ // Prevent users from interrupting the CPU while it is constructing its next command, this will only lead
+ // to infinite recursion.
+ if (isIntermediate) {
+ return
}
- return hasFinished
+ flush()
}
}
+
+ /**
+ * This class wraps a [command] with the timestamp it was started and possibly the task associated with it.
+ */
+ private data class CommandWrapper(val timestamp: Long, val command: SimResourceCommand)
}
diff --git a/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
index 5801fcd5..c7c3d3cc 100644
--- 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
@@ -22,8 +22,6 @@
package org.opendc.simulator.compute
-import kotlinx.coroutines.selects.SelectClause0
-import kotlinx.coroutines.selects.select
import java.time.Clock
/**
@@ -43,113 +41,10 @@ public interface SimExecutionContext {
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].
+ * Ask the host machine to interrupt the specified vCPU.
*
- * 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.
+ * @param cpu The id of the vCPU to interrupt.
+ * @throws IllegalArgumentException if the identifier points to a non-existing vCPU.
*/
- 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
- }
+ public fun interrupt(cpu: Int)
}
diff --git a/simulator/opendc-simulator/opendc-simulator-compute/src/main/kotlin/org/opendc/simulator/compute/SimFairShareHypervisor.kt b/simulator/opendc-simulator/opendc-simulator-compute/src/main/kotlin/org/opendc/simulator/compute/SimFairShareHypervisor.kt
new file mode 100644
index 00000000..5e86d32b
--- /dev/null
+++ b/simulator/opendc-simulator/opendc-simulator-compute/src/main/kotlin/org/opendc/simulator/compute/SimFairShareHypervisor.kt
@@ -0,0 +1,590 @@
+/*
+ * Copyright (c) 2021 AtLarge Research
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
+ * of this software and associated documentation files (the "Software"), to deal
+ * in the Software without restriction, including without limitation the rights
+ * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+ * copies of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included in all
+ * copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
+ * SOFTWARE.
+ */
+
+package org.opendc.simulator.compute
+
+import kotlinx.coroutines.flow.MutableStateFlow
+import kotlinx.coroutines.flow.StateFlow
+import kotlinx.coroutines.suspendCancellableCoroutine
+import org.opendc.simulator.compute.interference.PerformanceInterferenceModel
+import org.opendc.simulator.compute.model.ProcessingUnit
+import org.opendc.simulator.compute.workload.SimResourceCommand
+import org.opendc.simulator.compute.workload.SimWorkload
+import org.opendc.simulator.compute.workload.SimWorkloadBarrier
+import java.time.Clock
+import kotlin.coroutines.Continuation
+import kotlin.coroutines.resume
+import kotlin.coroutines.resumeWithException
+import kotlin.math.ceil
+import kotlin.math.max
+import kotlin.math.min
+
+/**
+ * A [SimHypervisor] that distributes the computing requirements of multiple [SimWorkload] on a single
+ * [SimBareMetalMachine] concurrently using weighted fair sharing.
+ *
+ * @param listener The hypervisor listener to use.
+ */
+public class SimFairShareHypervisor(private val listener: SimHypervisor.Listener? = null) : SimHypervisor {
+
+ override fun onStart(ctx: SimExecutionContext) {
+ val model = ctx.machine
+ this.ctx = ctx
+ this.commands = Array(model.cpus.size) { SimResourceCommand.Idle() }
+ this.pCpus = model.cpus.indices.sortedBy { model.cpus[it].frequency }.toIntArray()
+ this.maxUsage = model.cpus.sumByDouble { it.frequency }
+ this.barrier = SimWorkloadBarrier(model.cpus.size)
+ }
+
+ override fun onStart(ctx: SimExecutionContext, cpu: Int): SimResourceCommand {
+ return commands[cpu]
+ }
+
+ override fun onNext(ctx: SimExecutionContext, cpu: Int, remainingWork: Double): SimResourceCommand {
+ totalRemainingWork += remainingWork
+ val isLast = barrier.enter()
+
+ // Flush the progress of the guest after the barrier has been reached.
+ if (isLast && isDirty) {
+ isDirty = false
+ flushGuests()
+ }
+
+ return if (isDirty) {
+ // Wait for the scheduler determine the work after the barrier has been reached by all CPUs.
+ SimResourceCommand.Idle()
+ } else {
+ // Indicate that the scheduler needs to run next call.
+ if (isLast) {
+ isDirty = true
+ }
+
+ commands[cpu]
+ }
+ }
+
+ override fun canFit(model: SimMachineModel): Boolean = true
+
+ override fun createMachine(
+ model: SimMachineModel,
+ performanceInterferenceModel: PerformanceInterferenceModel?
+ ): SimMachine = SimVm(model, performanceInterferenceModel)
+
+ /**
+ * The execution context in which the hypervisor runs.
+ */
+ private lateinit var ctx: SimExecutionContext
+
+ /**
+ * The commands to submit to the underlying host.
+ */
+ private lateinit var commands: Array<SimResourceCommand>
+
+ /**
+ * The active vCPUs.
+ */
+ private val vcpus: MutableList<VCpu> = mutableListOf()
+
+ /**
+ * The indices of the physical CPU ordered by their speed.
+ */
+ private lateinit var pCpus: IntArray
+
+ /**
+ * The maximum amount of work to be performed per second.
+ */
+ private var maxUsage: Double = 0.0
+
+ /**
+ * The current load on the hypervisor.
+ */
+ private var load: Double = 0.0
+
+ /**
+ * The total amount of remaining work (of all pCPUs).
+ */
+ private var totalRemainingWork: Double = 0.0
+
+ /**
+ * The total speed requested by the vCPUs.
+ */
+ private var totalRequestedSpeed = 0.0
+
+ /**
+ * The total amount of work requested by the vCPUs.
+ */
+ private var totalRequestedWork = 0.0
+
+ /**
+ * The total allocated speed for the vCPUs.
+ */
+ private var totalAllocatedSpeed = 0.0
+
+ /**
+ * The total allocated work requested for the vCPUs.
+ */
+ private var totalAllocatedWork = 0.0
+
+ /**
+ * The amount of work that could not be performed due to over-committing resources.
+ */
+ private var totalOvercommittedWork = 0.0
+
+ /**
+ * The amount of work that was lost due to interference.
+ */
+ private var totalInterferedWork = 0.0
+
+ /**
+ * A flag to indicate that the scheduler has submitted work that has not yet been completed.
+ */
+ private var isDirty: Boolean = false
+
+ /**
+ * The scheduler barrier.
+ */
+ private lateinit var barrier: SimWorkloadBarrier
+
+ /**
+ * Indicate that the workloads should be re-scheduled.
+ */
+ private fun shouldSchedule() {
+ isDirty = true
+ ctx.interruptAll()
+ }
+
+ /**
+ * Schedule the work over the physical CPUs.
+ */
+ private fun doSchedule() {
+ // If there is no work yet, mark all pCPUs as idle.
+ if (vcpus.isEmpty()) {
+ commands.fill(SimResourceCommand.Idle())
+ ctx.interruptAll()
+ }
+
+ var duration: Double = Double.MAX_VALUE
+ var deadline: Long = Long.MAX_VALUE
+ var availableSpeed = maxUsage
+ var totalRequestedSpeed = 0.0
+ var totalRequestedWork = 0.0
+
+ // Sort the vCPUs based on their requested usage
+ // Profiling shows that it is faster to sort every slice instead of maintaining some kind of sorted set
+ vcpus.sort()
+
+ // Divide the available host capacity fairly across the vCPUs using max-min fair sharing
+ val vcpuIterator = vcpus.listIterator()
+ var remaining = vcpus.size
+ while (vcpuIterator.hasNext()) {
+ val vcpu = vcpuIterator.next()
+ val availableShare = availableSpeed / remaining--
+
+ when (val command = vcpu.command) {
+ is SimResourceCommand.Idle -> {
+ // Take into account the minimum deadline of this slice before we possible continue
+ deadline = min(deadline, command.deadline)
+
+ vcpu.actualSpeed = 0.0
+ }
+ is SimResourceCommand.Consume -> {
+ val grantedSpeed = min(vcpu.allowedSpeed, availableShare)
+
+ // Take into account the minimum deadline of this slice before we possible continue
+ deadline = min(deadline, command.deadline)
+
+ // Ignore idle computation
+ if (grantedSpeed <= 0.0 || command.work <= 0.0) {
+ vcpu.actualSpeed = 0.0
+ continue
+ }
+
+ totalRequestedSpeed += command.limit
+ totalRequestedWork += command.work
+
+ vcpu.actualSpeed = grantedSpeed
+ availableSpeed -= grantedSpeed
+
+ // The duration that we want to run is that of the shortest request from a vCPU
+ duration = min(duration, command.work / grantedSpeed)
+ }
+ SimResourceCommand.Exit -> {
+ // Apparently the vCPU has exited, so remove it from the scheduling queue.
+ vcpuIterator.remove()
+ }
+ }
+ }
+
+ // Round the duration to milliseconds
+ duration = ceil(duration * 1000) / 1000
+
+ assert(deadline >= ctx.clock.millis()) { "Deadline already passed" }
+
+ val totalAllocatedSpeed = maxUsage - availableSpeed
+ var totalAllocatedWork = 0.0
+ availableSpeed = totalAllocatedSpeed
+ load = totalAllocatedSpeed / maxUsage
+
+ // Divide the requests over the available capacity of the pCPUs fairly
+ for (i in pCpus) {
+ val maxCpuUsage = ctx.machine.cpus[i].frequency
+ val fraction = maxCpuUsage / maxUsage
+ val grantedSpeed = min(maxCpuUsage, totalAllocatedSpeed * fraction)
+ val grantedWork = duration * grantedSpeed
+
+ commands[i] =
+ if (grantedWork > 0.0 && grantedSpeed > 0.0)
+ SimResourceCommand.Consume(grantedWork, grantedSpeed, deadline)
+ else
+ SimResourceCommand.Idle(deadline)
+
+ totalAllocatedWork += grantedWork
+ availableSpeed -= grantedSpeed
+ }
+
+ this.totalRequestedSpeed = totalRequestedSpeed
+ this.totalRequestedWork = totalRequestedWork
+ this.totalAllocatedSpeed = totalAllocatedSpeed
+ this.totalAllocatedWork = totalAllocatedWork
+
+ ctx.interruptAll()
+ }
+
+ /**
+ * Flush the progress of the vCPUs.
+ */
+ private fun flushGuests() {
+ // Flush all the vCPUs work
+ for (vcpu in vcpus) {
+ vcpu.flush(interrupt = false)
+ }
+
+ // Report metrics
+ listener?.onSliceFinish(
+ this,
+ totalRequestedWork.toLong(),
+ (totalAllocatedWork - totalRemainingWork).toLong(),
+ totalOvercommittedWork.toLong(),
+ totalInterferedWork.toLong(),
+ totalRequestedSpeed,
+ totalAllocatedSpeed
+ )
+ totalRemainingWork = 0.0
+ totalInterferedWork = 0.0
+ totalOvercommittedWork = 0.0
+
+ // Force all pCPUs to re-schedule their work.
+ doSchedule()
+ }
+
+ /**
+ * Interrupt all host CPUs.
+ */
+ private fun SimExecutionContext.interruptAll() {
+ for (i in machine.cpus.indices) {
+ interrupt(i)
+ }
+ }
+
+ /**
+ * A virtual machine running on the hypervisor.
+ *
+ * @property model The machine model of the virtual machine.
+ * @property performanceInterferenceModel The performance interference model to utilize.
+ */
+ private inner class SimVm(
+ override val model: SimMachineModel,
+ val performanceInterferenceModel: PerformanceInterferenceModel? = null,
+ ) : SimMachine {
+ /**
+ * A [StateFlow] representing the CPU usage of the simulated machine.
+ */
+ override val usage: MutableStateFlow<Double> = MutableStateFlow(0.0)
+
+ /**
+ * A flag to indicate that the machine is terminated.
+ */
+ private var isTerminated = false
+
+ /**
+ * The current active workload.
+ */
+ private var cont: Continuation<Unit>? = null
+
+ /**
+ * The active CPUs of this virtual machine.
+ */
+ private var cpus: List<VCpu> = emptyList()
+
+ /**
+ * The execution context in which the workload runs.
+ */
+ val ctx = object : SimExecutionContext {
+ override val machine: SimMachineModel
+ get() = model
+
+ override val clock: Clock
+ get() = this@SimFairShareHypervisor.ctx.clock
+
+ override fun interrupt(cpu: Int) {
+ require(cpu < cpus.size) { "Invalid CPU identifier" }
+ cpus[cpu].interrupt()
+ }
+ }
+
+ /**
+ * Run the specified [SimWorkload] on this machine and suspend execution util the workload has finished.
+ */
+ override suspend fun run(workload: SimWorkload) {
+ require(!isTerminated) { "Machine is terminated" }
+ require(cont == null) { "Run should not be called concurrently" }
+
+ workload.onStart(ctx)
+
+ return suspendCancellableCoroutine { cont ->
+ this.cont = cont
+ this.cpus = model.cpus.map { VCpu(this, it, workload) }
+
+ for (cpu in cpus) {
+ // Register vCPU to scheduler
+ vcpus.add(cpu)
+
+ cpu.start()
+ }
+
+ // Re-schedule the work over the pCPUs
+ shouldSchedule()
+ }
+ }
+
+ /**
+ * Terminate this VM instance.
+ */
+ override fun close() {
+ isTerminated = true
+ }
+
+ /**
+ * Update the usage of the VM.
+ */
+ fun updateUsage() {
+ usage.value = cpus.sumByDouble { it.actualSpeed } / cpus.sumByDouble { it.model.frequency }
+ }
+
+ /**
+ * This method is invoked when one of the CPUs has exited.
+ */
+ fun onCpuExit(cpu: Int) {
+ // Check whether all other CPUs have finished
+ if (cpus.all { it.hasExited }) {
+ val cont = cont
+ this.cont = null
+ cont?.resume(Unit)
+ }
+ }
+
+ /**
+ * This method is invoked when one of the CPUs failed.
+ */
+ fun onCpuFailure(e: Throwable) {
+ // In case the flush fails with an exception, immediately propagate to caller, cancelling all other
+ // tasks.
+ val cont = cont
+ this.cont = null
+ cont?.resumeWithException(e)
+ }
+ }
+
+ /**
+ * A CPU of the virtual machine.
+ */
+ private inner class VCpu(val vm: SimVm, val model: ProcessingUnit, val workload: SimWorkload) : Comparable<VCpu> {
+ /**
+ * The latest command processed by the CPU.
+ */
+ var command: SimResourceCommand = SimResourceCommand.Idle()
+
+ /**
+ * The latest timestamp at which the vCPU was flushed.
+ */
+ var latestFlush: Long = 0
+
+ /**
+ * The processing speed that is allowed by the model constraints.
+ */
+ var allowedSpeed: Double = 0.0
+
+ /**
+ * The actual processing speed.
+ */
+ var actualSpeed: Double = 0.0
+ set(value) {
+ field = value
+ vm.updateUsage()
+ }
+
+ /**
+ * A flag to indicate that the CPU is currently processing a command.
+ */
+ var isIntermediate: Boolean = false
+
+ /**
+ * A flag to indicate that the CPU has exited.
+ */
+ val hasExited: Boolean
+ get() = command is SimResourceCommand.Exit
+
+ /**
+ * Process the specified [SimResourceCommand] for this CPU.
+ */
+ fun process(command: SimResourceCommand) {
+ // Assign command as the most recent executed command
+ this.command = command
+
+ when (command) {
+ is SimResourceCommand.Idle -> {
+ require(command.deadline >= ctx.clock.millis()) { "Deadline already passed" }
+
+ allowedSpeed = 0.0
+ }
+ is SimResourceCommand.Consume -> {
+ require(command.deadline >= ctx.clock.millis()) { "Deadline already passed" }
+
+ allowedSpeed = min(model.frequency, command.limit)
+ }
+ is SimResourceCommand.Exit -> {
+ allowedSpeed = 0.0
+ actualSpeed = 0.0
+
+ vm.onCpuExit(model.id)
+ }
+ }
+ }
+
+ /**
+ * Start the CPU.
+ */
+ fun start() {
+ try {
+ isIntermediate = true
+ latestFlush = ctx.clock.millis()
+
+ process(workload.onStart(vm.ctx, model.id))
+ } catch (e: Throwable) {
+ fail(e)
+ } finally {
+ isIntermediate = false
+ }
+ }
+
+ /**
+ * Flush the work performed by the CPU.
+ */
+ fun flush(interrupt: Boolean) {
+ val now = ctx.clock.millis()
+
+ // Fast path: if the CPU was already flushed at at the current instant, no need to flush the progress.
+ if (latestFlush >= now) {
+ return
+ }
+
+ try {
+ isIntermediate = true
+ when (val command = command) {
+ is SimResourceCommand.Idle -> {
+ // Act like nothing has happened in case the vCPU did not reach its deadline or was not
+ // interrupted by the user.
+ if (interrupt || command.deadline <= now) {
+ process(workload.onNext(vm.ctx, model.id, 0.0))
+ }
+ }
+ is SimResourceCommand.Consume -> {
+ // Apply performance interference model
+ val performanceScore = vm.performanceInterferenceModel?.apply(load) ?: 1.0
+
+ // Compute the remaining amount of work
+ val remainingWork = if (command.work > 0.0) {
+ // Compute the fraction of compute time allocated to the VM
+ val fraction = actualSpeed / totalAllocatedSpeed
+
+ // Compute the work that was actually granted to the VM.
+ val processingAvailable = max(0.0, totalAllocatedWork - totalRemainingWork) * fraction
+ val processed = processingAvailable * performanceScore
+
+ val interferedWork = processingAvailable - processed
+ totalInterferedWork += interferedWork
+
+ max(0.0, command.work - processed)
+ } else {
+ 0.0
+ }
+
+ // Act like nothing has happened in case the vCPU did not finish yet or was not interrupted by
+ // the user.
+ if (interrupt || remainingWork == 0.0 || command.deadline <= now) {
+ if (!interrupt) {
+ totalOvercommittedWork += remainingWork
+ }
+
+ process(workload.onNext(vm.ctx, model.id, remainingWork))
+ } else {
+ process(SimResourceCommand.Consume(remainingWork, command.limit, command.deadline))
+ }
+ }
+ SimResourceCommand.Exit ->
+ throw IllegalStateException()
+ }
+ } catch (e: Throwable) {
+ fail(e)
+ } finally {
+ latestFlush = now
+ isIntermediate = false
+ }
+ }
+
+ /**
+ * Interrupt the CPU.
+ */
+ fun interrupt() {
+ // Prevent users from interrupting the CPU while it is constructing its next command, this will only lead
+ // to infinite recursion.
+ if (isIntermediate) {
+ return
+ }
+
+ flush(interrupt = true)
+
+ // Force the scheduler to re-schedule
+ shouldSchedule()
+ }
+
+ /**
+ * Fail the CPU.
+ */
+ fun fail(e: Throwable) {
+ command = SimResourceCommand.Exit
+ vm.onCpuFailure(e)
+ }
+
+ override fun compareTo(other: VCpu): Int = allowedSpeed.compareTo(other.allowedSpeed)
+ }
+}
diff --git a/simulator/opendc-simulator/opendc-simulator-compute/src/main/kotlin/org/opendc/simulator/compute/SimFairShareHypervisorProvider.kt b/simulator/opendc-simulator/opendc-simulator-compute/src/main/kotlin/org/opendc/simulator/compute/SimFairShareHypervisorProvider.kt
new file mode 100644
index 00000000..02eb6ad0
--- /dev/null
+++ b/simulator/opendc-simulator/opendc-simulator-compute/src/main/kotlin/org/opendc/simulator/compute/SimFairShareHypervisorProvider.kt
@@ -0,0 +1,32 @@
+/*
+ * Copyright (c) 2021 AtLarge Research
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
+ * of this software and associated documentation files (the "Software"), to deal
+ * in the Software without restriction, including without limitation the rights
+ * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+ * copies of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included in all
+ * copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
+ * SOFTWARE.
+ */
+
+package org.opendc.simulator.compute
+
+/**
+ * A [SimHypervisorProvider] for the [SimFairShareHypervisor] implementation.
+ */
+public class SimFairShareHypervisorProvider : SimHypervisorProvider {
+ override val id: String = "fair-share"
+
+ override fun create(listener: SimHypervisor.Listener?): SimHypervisor = SimFairShareHypervisor(listener)
+}
diff --git a/simulator/opendc-simulator/opendc-simulator-compute/src/main/kotlin/org/opendc/simulator/compute/SimHypervisor.kt b/simulator/opendc-simulator/opendc-simulator-compute/src/main/kotlin/org/opendc/simulator/compute/SimHypervisor.kt
index 6087227b..d8f00bef 100644
--- a/simulator/opendc-simulator/opendc-simulator-compute/src/main/kotlin/org/opendc/simulator/compute/SimHypervisor.kt
+++ b/simulator/opendc-simulator/opendc-simulator-compute/src/main/kotlin/org/opendc/simulator/compute/SimHypervisor.kt
@@ -22,267 +22,29 @@
package org.opendc.simulator.compute
-import kotlinx.coroutines.*
-import kotlinx.coroutines.channels.Channel
-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 kotlinx.coroutines.selects.select
import org.opendc.simulator.compute.interference.PerformanceInterferenceModel
-import org.opendc.simulator.compute.model.ProcessingUnit
import org.opendc.simulator.compute.workload.SimWorkload
-import java.time.Clock
-import kotlin.math.ceil
-import kotlin.math.max
-import kotlin.math.min
/**
- * SimHypervisor distributes the computing requirements of multiple [SimWorkload] on a single [SimBareMetalMachine] concurrently.
- *
- * @param coroutineScope The [CoroutineScope] to run the simulated workloads in.
- * @param clock The virtual clock to track the simulation time.
+ * A SimHypervisor facilitates the execution of multiple concurrent [SimWorkload]s, while acting as a single workload
+ * to a [SimBareMetalMachine].
*/
-@OptIn(ExperimentalCoroutinesApi::class, InternalCoroutinesApi::class)
-public class SimHypervisor(
- private val coroutineScope: CoroutineScope,
- private val clock: Clock,
- private val listener: Listener? = null
-) : SimWorkload {
+public interface SimHypervisor : SimWorkload {
/**
- * A set for tracking the VM context objects.
+ * Determine whether the specified machine characterized by [model] can fit on this hypervisor at this moment.
*/
- private val vms: MutableSet<VmExecutionContext> = mutableSetOf()
-
- /**
- * A flag to indicate the driver is stopped.
- */
- private var stopped: Boolean = false
-
- /**
- * The channel for scheduling new CPU requests.
- */
- private val schedulingQueue = Channel<SchedulerCommand>(Channel.UNLIMITED)
+ public fun canFit(model: SimMachineModel): Boolean
/**
* Create a [SimMachine] instance on which users may run a [SimWorkload].
*
* @param model The machine to create.
+ * @param performanceInterferenceModel The performance interference model to use.
*/
- public fun createMachine(model: SimMachineModel, performanceInterferenceModel: PerformanceInterferenceModel? = null): SimMachine {
- val vm = VmSession(model, performanceInterferenceModel)
- val vmCtx = VmExecutionContext(vm)
-
- return object : SimMachine {
- override val model: SimMachineModel
- get() = vmCtx.machine
-
- override val usage: StateFlow<Double>
- get() = vm.usage
-
- /**
- * The current active workload.
- */
- private var activeWorkload: SimWorkload? = null
-
- override suspend fun run(workload: SimWorkload) {
- require(activeWorkload == null) { "Run should not be called concurrently" }
-
- try {
- activeWorkload = workload
- workload.run(vmCtx)
- } finally {
- activeWorkload = null
- }
- }
-
- override fun toString(): String = "SimVirtualMachine"
- }
- }
-
- /**
- * Run the scheduling process of the hypervisor.
- */
- override suspend fun run(ctx: SimExecutionContext) {
- val model = ctx.machine
- val maxUsage = model.cpus.sumByDouble { it.frequency }
- val pCPUs = model.cpus.indices.sortedBy { model.cpus[it].frequency }
-
- val vms = mutableSetOf<VmSession>()
- val vcpus = mutableListOf<VCpu>()
-
- val usage = DoubleArray(model.cpus.size)
- val burst = LongArray(model.cpus.size)
-
- fun process(command: SchedulerCommand) {
- when (command) {
- is SchedulerCommand.Schedule -> {
- vms += command.vm
- vcpus.addAll(command.vm.vcpus)
- }
- is SchedulerCommand.Deschedule -> {
- vms -= command.vm
- vcpus.removeAll(command.vm.vcpus)
- }
- is SchedulerCommand.Interrupt -> {
- }
- }
- }
-
- fun processRemaining() {
- var command = schedulingQueue.poll()
- while (command != null) {
- process(command)
- command = schedulingQueue.poll()
- }
- }
-
- while (!stopped) {
- // Wait for a request to be submitted if we have no work yet.
- if (vcpus.isEmpty()) {
- process(schedulingQueue.receive())
- }
-
- processRemaining()
-
- val start = clock.millis()
-
- var duration: Double = Double.POSITIVE_INFINITY
- var deadline: Long = Long.MAX_VALUE
- var availableUsage = maxUsage
- var totalRequestedUsage = 0.0
- var totalRequestedBurst = 0L
-
- // Sort the vCPUs based on their requested usage
- // Profiling shows that it is faster to sort every slice instead of maintaining some kind of sorted set
- vcpus.sort()
-
- // Divide the available host capacity fairly across the vCPUs using max-min fair sharing
- for ((i, req) in vcpus.withIndex()) {
- val remaining = vcpus.size - i
- val availableShare = availableUsage / remaining
- val grantedUsage = min(req.limit, availableShare)
-
- // Take into account the minimum deadline of this slice before we possible continue
- deadline = min(deadline, req.vm.deadline)
-
- // Ignore empty CPUs
- if (grantedUsage <= 0 || req.burst <= 0) {
- req.allocatedLimit = 0.0
- continue
- }
-
- totalRequestedUsage += req.limit
- totalRequestedBurst += req.burst
-
- req.allocatedLimit = grantedUsage
- availableUsage -= grantedUsage
-
- // The duration that we want to run is that of the shortest request from a vCPU
- duration = min(duration, req.burst / grantedUsage)
- }
-
- // XXX We set the minimum duration to 5 minutes here to prevent the rounding issues that are occurring with the FLOPs.
- duration = 300.0
-
- val totalAllocatedUsage = maxUsage - availableUsage
- var totalAllocatedBurst = 0L
- availableUsage = totalAllocatedUsage
- val serverLoad = totalAllocatedUsage / maxUsage
-
- // Divide the requests over the available capacity of the pCPUs fairly
- for (i in pCPUs) {
- val maxCpuUsage = model.cpus[i].frequency
- val fraction = maxCpuUsage / maxUsage
- val grantedUsage = min(maxCpuUsage, totalAllocatedUsage * fraction)
- val grantedBurst = ceil(duration * grantedUsage).toLong()
-
- usage[i] = grantedUsage
- burst[i] = grantedBurst
- totalAllocatedBurst += grantedBurst
- availableUsage -= grantedUsage
- }
-
- // We run the total burst on the host processor. Note that this call may be cancelled at any moment in
- // time, so not all of the burst may be executed.
- select<Boolean> {
- schedulingQueue.onReceive { schedulingQueue.offer(it); true }
- ctx.onRun(SimExecutionContext.Slice(burst, usage, deadline), SimExecutionContext.TriggerMode.DEADLINE)
- .invoke { false }
- }
-
- val end = clock.millis()
-
- // No work was performed
- if ((end - start) <= 0) {
- continue
- }
-
- // The total requested burst that the VMs wanted to run in the time-frame that we ran.
- val totalRequestedSubBurst =
- vcpus.map { ceil((duration * 1000) / (it.vm.deadline - start) * it.burst).toLong() }.sum()
- val totalRemainder = burst.sum()
- val totalGrantedBurst = totalAllocatedBurst - totalRemainder
-
- // The burst that was lost due to overcommissioning of CPU resources
- var totalOvercommissionedBurst = 0L
- // The burst that was lost due to interference.
- var totalInterferedBurst = 0L
-
- val vmIterator = vms.iterator()
- while (vmIterator.hasNext()) {
- val vm = vmIterator.next()
-
- // Apply performance interference model
- val performanceScore = vm.performanceInterferenceModel?.apply(serverLoad) ?: 1.0
- var hasFinished = false
-
- for (vcpu in vm.vcpus) {
- // Compute the fraction of compute time allocated to the VM
- val fraction = vcpu.allocatedLimit / totalAllocatedUsage
-
- // Compute the burst time that the VM was actually granted
- val grantedBurst = ceil(totalGrantedBurst * fraction).toLong()
-
- // The burst that was actually used by the VM
- val usedBurst = ceil(grantedBurst * performanceScore).toLong()
-
- totalInterferedBurst += grantedBurst - usedBurst
-
- // Compute remaining burst time to be executed for the request
- if (vcpu.consume(usedBurst)) {
- hasFinished = true
- } else if (vm.deadline <= end) {
- // Request must have its entire burst consumed or otherwise we have overcommission
- // Note that we count the overcommissioned burst if the hypervisor has failed.
- totalOvercommissionedBurst += vcpu.burst
- }
- }
-
- if (hasFinished || vm.deadline <= end) {
- // Mark the VM as finished and deschedule the VMs if needed
- if (vm.finish()) {
- vmIterator.remove()
- vcpus.removeAll(vm.vcpus)
- }
- }
- }
-
- listener?.onSliceFinish(
- this,
- totalRequestedBurst,
- min(totalRequestedSubBurst, totalGrantedBurst), // We can run more than requested due to timing
- totalOvercommissionedBurst,
- totalInterferedBurst, // Might be smaller than zero due to FP rounding errors,
- min(
- totalAllocatedUsage,
- totalRequestedUsage
- ), // The allocated usage might be slightly higher due to FP rounding
- totalRequestedUsage
- )
- }
- }
+ public fun createMachine(
+ model: SimMachineModel,
+ performanceInterferenceModel: PerformanceInterferenceModel? = null
+ ): SimMachine
/**
* Event listener for hypervisor events.
@@ -293,243 +55,12 @@ public class SimHypervisor(
*/
public fun onSliceFinish(
hypervisor: SimHypervisor,
- requestedBurst: Long,
- grantedBurst: Long,
- overcommissionedBurst: Long,
- interferedBurst: Long,
+ requestedWork: Long,
+ grantedWork: Long,
+ overcommittedWork: Long,
+ interferedWork: Long,
cpuUsage: Double,
cpuDemand: Double
)
}
-
- /**
- * A scheduling command processed by the scheduler.
- */
- private sealed class SchedulerCommand {
- /**
- * Schedule the specified VM on the hypervisor.
- */
- data class Schedule(val vm: VmSession) : SchedulerCommand()
-
- /**
- * De-schedule the specified VM on the hypervisor.
- */
- data class Deschedule(val vm: VmSession) : SchedulerCommand()
-
- /**
- * Interrupt the scheduler.
- */
- object Interrupt : SchedulerCommand()
- }
-
- /**
- * A virtual machine running on the hypervisor.
- *
- * @param ctx The execution context the vCPU runs in.
- * @param triggerMode The mode when to trigger the VM exit.
- * @param merge The function to merge consecutive slices on spillover.
- * @param select The function to select on finish.
- */
- @OptIn(InternalCoroutinesApi::class)
- private data class VmSession(
- val model: SimMachineModel,
- val performanceInterferenceModel: PerformanceInterferenceModel? = null,
- var triggerMode: SimExecutionContext.TriggerMode = SimExecutionContext.TriggerMode.FIRST,
- var merge: (SimExecutionContext.Slice, SimExecutionContext.Slice) -> SimExecutionContext.Slice = { _, r -> r },
- var select: () -> Unit = {}
- ) {
- /**
- * The vCPUs of this virtual machine.
- */
- val vcpus: List<VCpu>
-
- /**
- * The slices that the VM wants to run.
- */
- var queue: Iterator<SimExecutionContext.Slice> = emptyList<SimExecutionContext.Slice>().iterator()
-
- /**
- * The current active slice.
- */
- var activeSlice: SimExecutionContext.Slice? = null
-
- /**
- * The current deadline of the VM.
- */
- val deadline: Long
- get() = activeSlice?.deadline ?: Long.MAX_VALUE
-
- /**
- * A flag to indicate that the VM is idle.
- */
- val isIdle: Boolean
- get() = activeSlice == null
-
- /**
- * The usage of the virtual machine.
- */
- val usage: MutableStateFlow<Double> = MutableStateFlow(0.0)
-
- init {
- vcpus = model.cpus.mapIndexed { i, model -> VCpu(this, model, i) }
- }
-
- /**
- * Schedule the given slices on this vCPU, replacing the existing slices.
- */
- fun schedule(slices: Sequence<SimExecutionContext.Slice>) {
- queue = slices.iterator()
-
- if (queue.hasNext()) {
- activeSlice = queue.next()
- refresh()
- }
- }
-
- /**
- * Cancel the existing workload on the VM.
- */
- fun cancel() {
- queue = emptyList<SimExecutionContext.Slice>().iterator()
- activeSlice = null
- refresh()
- }
-
- /**
- * Finish the current slice of the VM.
- *
- * @return `true` if the vCPUs may be descheduled, `false` otherwise.
- */
- fun finish(): Boolean {
- val activeSlice = activeSlice ?: return true
-
- return if (queue.hasNext()) {
- val needsMerge = activeSlice.burst.any { it > 0 }
- val candidateSlice = queue.next()
- val slice = if (needsMerge) merge(activeSlice, candidateSlice) else candidateSlice
-
- this.activeSlice = slice
-
- // Update the vCPU cache
- refresh()
-
- false
- } else {
- this.activeSlice = null
- select()
- true
- }
- }
-
- /**
- * Refresh the vCPU cache.
- */
- fun refresh() {
- vcpus.forEach { it.refresh() }
- usage.value = vcpus.sumByDouble { it.burst / it.limit } / vcpus.size
- }
- }
-
- /**
- * A virtual CPU that can be scheduled on a physical CPU.
- *
- * @param vm The VM of which this vCPU is part.
- * @param model The model of CPU that this vCPU models.
- * @param id The id of the vCPU with respect to the VM.
- */
- private data class VCpu(
- val vm: VmSession,
- val model: ProcessingUnit,
- val id: Int
- ) : Comparable<VCpu> {
- /**
- * The current limit on the vCPU.
- */
- var limit: Double = 0.0
-
- /**
- * The limit allocated by the hypervisor.
- */
- var allocatedLimit: Double = 0.0
-
- /**
- * The current burst running on the vCPU.
- */
- var burst: Long = 0L
-
- /**
- * Consume the specified burst on this vCPU.
- */
- fun consume(burst: Long): Boolean {
- this.burst = max(0, this.burst - burst)
-
- // Flush the result to the slice if it exists
- vm.activeSlice?.burst?.takeIf { id < it.size }?.set(id, this.burst)
-
- return allocatedLimit > 0.0 && this.burst == 0L
- }
-
- /**
- * Refresh the information of this vCPU based on the current slice.
- */
- fun refresh() {
- limit = vm.activeSlice?.limit?.takeIf { id < it.size }?.get(id) ?: 0.0
- burst = vm.activeSlice?.burst?.takeIf { id < it.size }?.get(id) ?: 0
- }
-
- /**
- * Compare to another vCPU based on the current load of the vCPU.
- */
- override fun compareTo(other: VCpu): Int {
- return limit.compareTo(other.limit)
- }
-
- /**
- * Create a string representation of the vCPU.
- */
- override fun toString(): String =
- "vCPU(id=$id,burst=$burst,limit=$limit,allocatedLimit=$allocatedLimit)"
- }
-
- /**
- * The execution context in which a VM runs.
- *
- */
- private inner class VmExecutionContext(val session: VmSession) :
- SimExecutionContext, DisposableHandle {
- override val machine: SimMachineModel
- get() = session.model
-
- override val clock: Clock
- get() = this@SimHypervisor.clock
-
- @OptIn(InternalCoroutinesApi::class)
- override fun onRun(
- batch: Sequence<SimExecutionContext.Slice>,
- triggerMode: SimExecutionContext.TriggerMode,
- merge: (SimExecutionContext.Slice, SimExecutionContext.Slice) -> SimExecutionContext.Slice
- ): SelectClause0 = object : SelectClause0 {
- @InternalCoroutinesApi
- override fun <R> registerSelectClause0(select: SelectInstance<R>, block: suspend () -> R) {
- session.triggerMode = triggerMode
- session.merge = merge
- session.select = {
- if (select.trySelect()) {
- block.startCoroutineCancellable(select.completion)
- }
- }
- session.schedule(batch)
- // Indicate to the hypervisor that the VM should be re-scheduled
- schedulingQueue.offer(SchedulerCommand.Schedule(session))
- select.disposeOnSelect(this@VmExecutionContext)
- }
- }
-
- override fun dispose() {
- if (!session.isIdle) {
- session.cancel()
- schedulingQueue.offer(SchedulerCommand.Deschedule(session))
- }
- }
- }
}
diff --git a/simulator/opendc-simulator/opendc-simulator-compute/src/main/kotlin/org/opendc/simulator/compute/SimHypervisorProvider.kt b/simulator/opendc-simulator/opendc-simulator-compute/src/main/kotlin/org/opendc/simulator/compute/SimHypervisorProvider.kt
new file mode 100644
index 00000000..a5b4526b
--- /dev/null
+++ b/simulator/opendc-simulator/opendc-simulator-compute/src/main/kotlin/org/opendc/simulator/compute/SimHypervisorProvider.kt
@@ -0,0 +1,41 @@
+/*
+ * Copyright (c) 2021 AtLarge Research
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
+ * of this software and associated documentation files (the "Software"), to deal
+ * in the Software without restriction, including without limitation the rights
+ * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+ * copies of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included in all
+ * copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
+ * SOFTWARE.
+ */
+
+package org.opendc.simulator.compute
+
+/**
+ * A service provider interface for constructing a [SimHypervisor].
+ */
+public interface SimHypervisorProvider {
+ /**
+ * A unique identifier for this hypervisor implementation.
+ *
+ * Each hypervisor must provide a unique ID, so that they can be selected by the user.
+ * When in doubt, you may use the fully qualified name of your custom [SimHypervisor] implementation class.
+ */
+ public val id: String
+
+ /**
+ * Create a [SimHypervisor] instance with the specified [listener].
+ */
+ public fun create(listener: SimHypervisor.Listener? = null): SimHypervisor
+}
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
index f66085af..ea8eeb37 100644
--- 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
@@ -22,15 +22,13 @@
package org.opendc.simulator.compute
-import kotlinx.coroutines.ExperimentalCoroutinesApi
import kotlinx.coroutines.flow.StateFlow
import org.opendc.simulator.compute.workload.SimWorkload
/**
* A generic machine that is able to run a [SimWorkload].
*/
-@OptIn(ExperimentalCoroutinesApi::class)
-public interface SimMachine {
+public interface SimMachine : AutoCloseable {
/**
* The model of the machine containing its specifications.
*/
@@ -45,4 +43,9 @@ public interface SimMachine {
* Run the specified [SimWorkload] on this machine and suspend execution util the workload has finished.
*/
public suspend fun run(workload: SimWorkload)
+
+ /**
+ * Terminate this machine.
+ */
+ public override fun close()
}
diff --git a/simulator/opendc-simulator/opendc-simulator-compute/src/main/kotlin/org/opendc/simulator/compute/SimSpaceSharedHypervisor.kt b/simulator/opendc-simulator/opendc-simulator-compute/src/main/kotlin/org/opendc/simulator/compute/SimSpaceSharedHypervisor.kt
new file mode 100644
index 00000000..66d3eda7
--- /dev/null
+++ b/simulator/opendc-simulator/opendc-simulator-compute/src/main/kotlin/org/opendc/simulator/compute/SimSpaceSharedHypervisor.kt
@@ -0,0 +1,284 @@
+/*
+ * Copyright (c) 2021 AtLarge Research
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
+ * of this software and associated documentation files (the "Software"), to deal
+ * in the Software without restriction, including without limitation the rights
+ * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+ * copies of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included in all
+ * copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
+ * SOFTWARE.
+ */
+
+package org.opendc.simulator.compute
+
+import kotlinx.coroutines.flow.MutableStateFlow
+import kotlinx.coroutines.flow.StateFlow
+import kotlinx.coroutines.suspendCancellableCoroutine
+import org.opendc.simulator.compute.interference.PerformanceInterferenceModel
+import org.opendc.simulator.compute.model.ProcessingUnit
+import org.opendc.simulator.compute.workload.SimResourceCommand
+import org.opendc.simulator.compute.workload.SimWorkload
+import java.time.Clock
+import java.util.ArrayDeque
+import kotlin.coroutines.Continuation
+import kotlin.coroutines.resume
+import kotlin.coroutines.resumeWithException
+import kotlin.math.min
+
+/**
+ * A [SimHypervisor] that allocates its sub-resources exclusively for the virtual machine that it hosts.
+ *
+ * @param listener The hypervisor listener to use.
+ */
+public class SimSpaceSharedHypervisor(private val listener: SimHypervisor.Listener? = null) : SimHypervisor {
+ /**
+ * The execution context in which the hypervisor runs.
+ */
+ private lateinit var ctx: SimExecutionContext
+
+ /**
+ * The mapping from pCPU to vCPU.
+ */
+ private lateinit var vcpus: Array<VCpu?>
+
+ /**
+ * The available physical CPUs to schedule on.
+ */
+ private val availableCpus = ArrayDeque<Int>()
+
+ override fun canFit(model: SimMachineModel): Boolean = availableCpus.size >= model.cpus.size
+
+ override fun createMachine(
+ model: SimMachineModel,
+ performanceInterferenceModel: PerformanceInterferenceModel?
+ ): SimMachine {
+ require(canFit(model)) { "Cannot fit machine" }
+ return SimVm(model, performanceInterferenceModel)
+ }
+
+ override fun onStart(ctx: SimExecutionContext) {
+ this.ctx = ctx
+ this.vcpus = arrayOfNulls(ctx.machine.cpus.size)
+ this.availableCpus.addAll(ctx.machine.cpus.indices)
+ }
+
+ override fun onStart(ctx: SimExecutionContext, cpu: Int): SimResourceCommand {
+ return onNext(ctx, cpu, 0.0)
+ }
+
+ override fun onNext(ctx: SimExecutionContext, cpu: Int, remainingWork: Double): SimResourceCommand {
+ return vcpus[cpu]?.next(0.0) ?: SimResourceCommand.Idle()
+ }
+
+ /**
+ * A virtual machine running on the hypervisor.
+ *
+ * @property model The machine model of the virtual machine.
+ * @property performanceInterferenceModel The performance interference model to utilize.
+ */
+ private inner class SimVm(
+ override val model: SimMachineModel,
+ val performanceInterferenceModel: PerformanceInterferenceModel? = null,
+ ) : SimMachine {
+ /**
+ * A flag to indicate that the machine is terminated.
+ */
+ private var isTerminated = false
+
+ /**
+ * A [StateFlow] representing the CPU usage of the simulated machine.
+ */
+ override val usage: MutableStateFlow<Double> = MutableStateFlow(0.0)
+
+ /**
+ * The current active workload.
+ */
+ private var cont: Continuation<Unit>? = null
+
+ /**
+ * The physical CPUs that have been allocated.
+ */
+ private val pCPUs = model.cpus.map { availableCpus.poll() }.toIntArray()
+
+ /**
+ * The active CPUs of this virtual machine.
+ */
+ private var cpus: List<VCpu> = emptyList()
+
+ /**
+ * The execution context in which the workload runs.
+ */
+ val ctx = object : SimExecutionContext {
+ override val machine: SimMachineModel
+ get() = model
+
+ override val clock: Clock
+ get() = this@SimSpaceSharedHypervisor.ctx.clock
+
+ override fun interrupt(cpu: Int) {
+ require(cpu < cpus.size) { "Invalid CPU identifier" }
+ cpus[cpu].interrupt()
+ }
+ }
+
+ /**
+ * Run the specified [SimWorkload] on this machine and suspend execution util the workload has finished.
+ */
+ override suspend fun run(workload: SimWorkload) {
+ require(!isTerminated) { "Machine is terminated" }
+ require(cont == null) { "Run should not be called concurrently" }
+
+ workload.onStart(ctx)
+
+ return suspendCancellableCoroutine { cont ->
+ this.cont = cont
+ this.cpus = model.cpus.mapIndexed { index, model -> VCpu(this, model, workload, pCPUs[index]) }
+
+ for (cpu in cpus) {
+ cpu.start()
+ }
+ }
+ }
+
+ override fun close() {
+ isTerminated = true
+ for (pCPU in pCPUs) {
+ vcpus[pCPU] = null
+ availableCpus.add(pCPU)
+ }
+ }
+
+ /**
+ * Update the usage of the VM.
+ */
+ fun updateUsage() {
+ usage.value = cpus.sumByDouble { it.speed } / cpus.sumByDouble { it.model.frequency }
+ }
+
+ /**
+ * This method is invoked when one of the CPUs has exited.
+ */
+ fun onCpuExit(cpu: Int) {
+ // Check whether all other CPUs have finished
+ if (cpus.all { it.hasExited }) {
+ val cont = cont
+ this.cont = null
+ cont?.resume(Unit)
+ }
+ }
+
+ /**
+ * This method is invoked when one of the CPUs failed.
+ */
+ fun onCpuFailure(e: Throwable) {
+ // In case the flush fails with an exception, immediately propagate to caller, cancelling all other
+ // tasks.
+ val cont = cont
+ this.cont = null
+ cont?.resumeWithException(e)
+ }
+ }
+
+ /**
+ * A CPU of the virtual machine.
+ */
+ private inner class VCpu(val vm: SimVm, val model: ProcessingUnit, val workload: SimWorkload, val pCPU: Int) {
+ /**
+ * The processing speed of the vCPU.
+ */
+ var speed: Double = 0.0
+ set(value) {
+ field = value
+ vm.updateUsage()
+ }
+
+ /**
+ * A flag to indicate that the CPU has exited.
+ */
+ var hasExited: Boolean = false
+
+ /**
+ * A flag to indicate that the CPU was started.
+ */
+ var hasStarted: Boolean = false
+
+ /**
+ * Process the specified [SimResourceCommand] for this CPU.
+ */
+ fun process(command: SimResourceCommand): SimResourceCommand {
+ return when (command) {
+ is SimResourceCommand.Idle -> {
+ speed = 0.0
+ command
+ }
+ is SimResourceCommand.Consume -> {
+ speed = min(model.frequency, command.limit)
+ command
+ }
+ is SimResourceCommand.Exit -> {
+ speed = 0.0
+ hasExited = true
+
+ vm.onCpuExit(model.id)
+
+ SimResourceCommand.Idle()
+ }
+ }
+ }
+
+ /**
+ * Start the CPU.
+ */
+ fun start() {
+ vcpus[pCPU] = this
+ interrupt()
+ }
+
+ /**
+ * Request the workload for more work.
+ */
+ fun next(remainingWork: Double): SimResourceCommand {
+ return try {
+ val command =
+ if (hasStarted) {
+ workload.onNext(ctx, model.id, remainingWork)
+ } else {
+ hasStarted = true
+ workload.onStart(ctx, model.id)
+ }
+ process(command)
+ } catch (e: Throwable) {
+ fail(e)
+ }
+ }
+
+ /**
+ * Interrupt the CPU.
+ */
+ fun interrupt() {
+ ctx.interrupt(pCPU)
+ }
+
+ /**
+ * Fail the CPU.
+ */
+ fun fail(e: Throwable): SimResourceCommand {
+ hasExited = true
+
+ vm.onCpuFailure(e)
+
+ return SimResourceCommand.Idle()
+ }
+ }
+}
diff --git a/simulator/opendc-simulator/opendc-simulator-compute/src/main/kotlin/org/opendc/simulator/compute/SimSpaceSharedHypervisorProvider.kt b/simulator/opendc-simulator/opendc-simulator-compute/src/main/kotlin/org/opendc/simulator/compute/SimSpaceSharedHypervisorProvider.kt
new file mode 100644
index 00000000..3d49e544
--- /dev/null
+++ b/simulator/opendc-simulator/opendc-simulator-compute/src/main/kotlin/org/opendc/simulator/compute/SimSpaceSharedHypervisorProvider.kt
@@ -0,0 +1,32 @@
+/*
+ * Copyright (c) 2021 AtLarge Research
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
+ * of this software and associated documentation files (the "Software"), to deal
+ * in the Software without restriction, including without limitation the rights
+ * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+ * copies of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included in all
+ * copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
+ * SOFTWARE.
+ */
+
+package org.opendc.simulator.compute
+
+/**
+ * A [SimHypervisorProvider] for the [SimSpaceSharedHypervisor] implementation.
+ */
+public class SimSpaceSharedHypervisorProvider : SimHypervisorProvider {
+ override val id: String = "space-shared"
+
+ override fun create(listener: SimHypervisor.Listener?): SimHypervisor = SimSpaceSharedHypervisor(listener)
+}
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
index 918a78bd..c22fcc07 100644
--- 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
@@ -23,35 +23,46 @@
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.
+ * A [SimWorkload] that models applications as a static number of floating point operations ([flops]) executed on
+ * multiple cores of a compute resource.
*
* @property flops The number of floating point operations to perform for this task in MFLOPs.
- * @property 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 }
+ override fun onStart(ctx: SimExecutionContext) {}
- ctx.run(SimExecutionContext.Slice(burst, maxUsage, Long.MAX_VALUE), triggerMode = SimExecutionContext.TriggerMode.LAST)
+ override fun onStart(ctx: SimExecutionContext, cpu: Int): SimResourceCommand {
+ val cores = ctx.machine.cpus.size
+ val limit = ctx.machine.cpus[cpu].frequency * utilization
+ val work = flops.toDouble() / cores
+
+ return if (work > 0.0) {
+ SimResourceCommand.Consume(work, limit)
+ } else {
+ SimResourceCommand.Exit
+ }
}
+
+ override fun onNext(ctx: SimExecutionContext, cpu: Int, remainingWork: Double): SimResourceCommand {
+ return if (remainingWork > 0.0) {
+ val limit = ctx.machine.cpus[cpu].frequency * utilization
+
+ return SimResourceCommand.Consume(remainingWork, limit)
+ } else {
+ SimResourceCommand.Exit
+ }
+ }
+
+ override fun toString(): String = "SimFlopsWorkload(FLOPs=$flops,utilization=$utilization)"
}
diff --git a/simulator/opendc-simulator/opendc-simulator-compute/src/main/kotlin/org/opendc/simulator/compute/workload/SimResourceCommand.kt b/simulator/opendc-simulator/opendc-simulator-compute/src/main/kotlin/org/opendc/simulator/compute/workload/SimResourceCommand.kt
new file mode 100644
index 00000000..41a5028e
--- /dev/null
+++ b/simulator/opendc-simulator/opendc-simulator-compute/src/main/kotlin/org/opendc/simulator/compute/workload/SimResourceCommand.kt
@@ -0,0 +1,52 @@
+/*
+ * Copyright (c) 2021 AtLarge Research
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
+ * of this software and associated documentation files (the "Software"), to deal
+ * in the Software without restriction, including without limitation the rights
+ * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+ * copies of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included in all
+ * copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
+ * SOFTWARE.
+ */
+
+package org.opendc.simulator.compute.workload
+
+/**
+ * A command that is sent to the host machine.
+ */
+public sealed class SimResourceCommand {
+ /**
+ * A request to the host to process the specified amount of [work] on a vCPU before the specified [deadline].
+ *
+ * @param work The amount of work to process on the CPU.
+ * @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) { "The amount of work must be positive." }
+ require(limit > 0) { "Limit must be positive." }
+ }
+ }
+
+ /**
+ * An indication to the host that the vCPU will idle until the specified [deadline] or is interrupted.
+ */
+ public data class Idle(val deadline: Long = Long.MAX_VALUE) : SimResourceCommand()
+
+ /**
+ * An indication to the host that the vCPU has finished processing.
+ */
+ public object Exit : SimResourceCommand()
+}
diff --git a/simulator/opendc-simulator/opendc-simulator-compute/src/main/kotlin/org/opendc/simulator/compute/workload/SimRuntimeWorkload.kt b/simulator/opendc-simulator/opendc-simulator-compute/src/main/kotlin/org/opendc/simulator/compute/workload/SimRuntimeWorkload.kt
new file mode 100644
index 00000000..00ebebce
--- /dev/null
+++ b/simulator/opendc-simulator/opendc-simulator-compute/src/main/kotlin/org/opendc/simulator/compute/workload/SimRuntimeWorkload.kt
@@ -0,0 +1,60 @@
+/*
+ * 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 [SimWorkload] that models application execution as a single duration.
+ *
+ * @property duration The duration of the workload.
+ * @property utilization The utilization of the application during runtime.
+ */
+public class SimRuntimeWorkload(
+ public val duration: Long,
+ public val utilization: Double = 0.8
+) : SimWorkload {
+ init {
+ require(duration >= 0) { "Duration must be non-negative" }
+ require(utilization > 0.0 && utilization <= 1.0) { "Utilization must be in (0, 1]" }
+ }
+
+ override fun onStart(ctx: SimExecutionContext) {}
+
+ override fun onStart(ctx: SimExecutionContext, cpu: Int): SimResourceCommand {
+ val limit = ctx.machine.cpus[cpu].frequency * utilization
+ val work = (limit / 1000) * duration
+ return SimResourceCommand.Consume(work, limit)
+ }
+
+ override fun onNext(ctx: SimExecutionContext, cpu: Int, remainingWork: Double): SimResourceCommand {
+ return if (remainingWork > 0.0) {
+ val limit = ctx.machine.cpus[cpu].frequency * utilization
+ SimResourceCommand.Consume(remainingWork, limit)
+ } else {
+ SimResourceCommand.Exit
+ }
+ }
+
+ override fun toString(): String = "SimRuntimeWorkload(duration=$duration,utilization=$utilization)"
+}
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
index 7b1ddf32..deb10b98 100644
--- 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
@@ -23,31 +23,64 @@
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(public 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)
+ private var offset = 0L
+ private val iterator = trace.iterator()
+ private var fragment: Fragment? = null
+ private lateinit var barrier: SimWorkloadBarrier
+
+ override fun onStart(ctx: SimExecutionContext) {
+ barrier = SimWorkloadBarrier(ctx.machine.cpus.size)
+ fragment = nextFragment()
+ offset = ctx.clock.millis()
+ }
+
+ override fun onStart(ctx: SimExecutionContext, cpu: Int): SimResourceCommand {
+ return onNext(ctx, cpu, 0.0)
+ }
+
+ override fun onNext(ctx: SimExecutionContext, cpu: Int, remainingWork: Double): SimResourceCommand {
+ val now = ctx.clock.millis()
+ val fragment = fragment ?: return SimResourceCommand.Exit
+ val work = (fragment.duration / 1000) * fragment.usage
+ val deadline = offset + fragment.duration
+
+ assert(deadline >= now) { "Deadline already passed" }
+
+ val cmd =
+ if (cpu < fragment.cores && work > 0.0)
+ SimResourceCommand.Consume(work, fragment.usage, deadline)
+ else
+ SimResourceCommand.Idle(deadline)
+
+ if (barrier.enter()) {
+ this.fragment = nextFragment()
+ this.offset += fragment.duration
}
- ctx.run(batch)
+ return cmd
}
override fun toString(): String = "SimTraceWorkload"
/**
+ * Obtain the next fragment.
+ */
+ private fun nextFragment(): Fragment? {
+ return if (iterator.hasNext()) {
+ iterator.next()
+ } else {
+ null
+ }
+ }
+
+ /**
* A fragment of the workload.
*/
- public data class Fragment(val time: Long, val flops: Long, val duration: Long, val usage: Double, val cores: Int)
+ public data class Fragment(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
index 2add8cce..6fc78d56 100644
--- 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
@@ -28,14 +28,31 @@ import org.opendc.simulator.compute.SimExecutionContext
* A model that characterizes the runtime behavior of some particular workload.
*
* Workloads are stateful objects that may be paused and resumed at a later moment. As such, be careful when using the
- * same [SimWorkload] from multiple contexts as only a single concurrent [run] call is expected.
+ * same [SimWorkload] from multiple contexts.
*/
public interface SimWorkload {
/**
- * Launch the workload in the specified [SimExecutionContext].
+ * This method is invoked when the workload is started, before the (virtual) CPUs assigned to the workload will
+ * start.
+ */
+ public fun onStart(ctx: SimExecutionContext)
+
+ /**
+ * This method is invoked when a (virtual) CPU assigned to the workload has started.
+ *
+ * @param ctx The execution context in which the workload runs.
+ * @param cpu The index of the (virtual) CPU to start.
+ * @return The command to perform on the CPU.
+ */
+ public fun onStart(ctx: SimExecutionContext, cpu: Int): SimResourceCommand
+
+ /**
+ * This method is invoked when a (virtual) CPU assigned to the workload was interrupted or reached its deadline.
*
- * 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.
+ * @param ctx The execution context in which the workload runs.
+ * @param cpu The index of the (virtual) CPU to obtain the resource consumption of.
+ * @param remainingWork The remaining work that was not yet completed.
+ * @return The next command to perform on the CPU.
*/
- public suspend fun run(ctx: SimExecutionContext)
+ public fun onNext(ctx: SimExecutionContext, cpu: Int, remainingWork: Double): SimResourceCommand
}
diff --git a/simulator/opendc-simulator/opendc-simulator-compute/src/main/kotlin/org/opendc/simulator/compute/workload/SimWorkloadBarrier.kt b/simulator/opendc-simulator/opendc-simulator-compute/src/main/kotlin/org/opendc/simulator/compute/workload/SimWorkloadBarrier.kt
new file mode 100644
index 00000000..45a299be
--- /dev/null
+++ b/simulator/opendc-simulator/opendc-simulator-compute/src/main/kotlin/org/opendc/simulator/compute/workload/SimWorkloadBarrier.kt
@@ -0,0 +1,45 @@
+/*
+ * Copyright (c) 2021 AtLarge Research
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
+ * of this software and associated documentation files (the "Software"), to deal
+ * in the Software without restriction, including without limitation the rights
+ * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+ * copies of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included in all
+ * copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
+ * SOFTWARE.
+ */
+
+package org.opendc.simulator.compute.workload
+
+/**
+ * The [SimWorkloadBarrier] is a barrier that allows workloads to wait for a select number of CPUs to complete, before
+ * proceeding its operation.
+ */
+public class SimWorkloadBarrier(public val parties: Int) {
+ private var counter = 0
+
+ /**
+ * Enter the barrier and determine whether the caller is the last to reach the barrier.
+ *
+ * @return `true` if the caller is the last to reach the barrier, `false` otherwise.
+ */
+ public fun enter(): Boolean {
+ val last = ++counter == parties
+ if (last) {
+ counter = 0
+ return true
+ }
+ return false
+ }
+}
diff --git a/simulator/opendc-simulator/opendc-simulator-compute/src/test/kotlin/org/opendc/simulator/compute/SimHypervisorTest.kt b/simulator/opendc-simulator/opendc-simulator-compute/src/test/kotlin/org/opendc/simulator/compute/SimHypervisorTest.kt
index 78bd2940..b8eee4f0 100644
--- a/simulator/opendc-simulator/opendc-simulator-compute/src/test/kotlin/org/opendc/simulator/compute/SimHypervisorTest.kt
+++ b/simulator/opendc-simulator/opendc-simulator-compute/src/test/kotlin/org/opendc/simulator/compute/SimHypervisorTest.kt
@@ -26,7 +26,7 @@ import kotlinx.coroutines.ExperimentalCoroutinesApi
import kotlinx.coroutines.launch
import kotlinx.coroutines.test.TestCoroutineScope
import kotlinx.coroutines.yield
-import org.junit.jupiter.api.Assertions
+import org.junit.jupiter.api.Assertions.assertEquals
import org.junit.jupiter.api.BeforeEach
import org.junit.jupiter.api.Test
import org.junit.jupiter.api.assertAll
@@ -51,7 +51,7 @@ internal class SimHypervisorTest {
scope = TestCoroutineScope()
clock = DelayControllerClockAdapter(scope)
- val cpuNode = ProcessingNode("Intel", "Xeon", "amd64", 2)
+ val cpuNode = ProcessingNode("Intel", "Xeon", "amd64", 1)
machineModel = SimMachineModel(
cpus = List(cpuNode.coreCount) { ProcessingUnit(cpuNode, it, 3200.0) },
memory = List(4) { MemoryUnit("Crucial", "MTA18ASF4G72AZ-3G2B1", 3200.0, 32_000) }
@@ -59,27 +59,27 @@ internal class SimHypervisorTest {
}
/**
- * Test overcommissioning of a hypervisor.
+ * Test overcommitting of resources via the hypervisor with a single VM.
*/
@Test
- fun overcommission() {
+ fun testOvercommittedSingle() {
val listener = object : SimHypervisor.Listener {
- var totalRequestedBurst = 0L
- var totalGrantedBurst = 0L
- var totalOvercommissionedBurst = 0L
+ var totalRequestedWork = 0L
+ var totalGrantedWork = 0L
+ var totalOvercommittedWork = 0L
override fun onSliceFinish(
hypervisor: SimHypervisor,
- requestedBurst: Long,
- grantedBurst: Long,
- overcommissionedBurst: Long,
- interferedBurst: Long,
+ requestedWork: Long,
+ grantedWork: Long,
+ overcommittedWork: Long,
+ interferedWork: Long,
cpuUsage: Double,
cpuDemand: Double
) {
- totalRequestedBurst += requestedBurst
- totalGrantedBurst += grantedBurst
- totalOvercommissionedBurst += overcommissionedBurst
+ totalRequestedWork += requestedWork
+ totalGrantedWork += grantedWork
+ totalOvercommittedWork += overcommittedWork
}
}
@@ -88,24 +88,84 @@ internal class SimHypervisorTest {
val workloadA =
SimTraceWorkload(
sequenceOf(
- SimTraceWorkload.Fragment(0, 28L * duration, duration * 1000, 28.0, 2),
- SimTraceWorkload.Fragment(0, 3500L * duration, duration * 1000, 3500.0, 2),
- SimTraceWorkload.Fragment(0, 0, duration * 1000, 0.0, 2),
- SimTraceWorkload.Fragment(0, 183L * duration, duration * 1000, 183.0, 2)
+ SimTraceWorkload.Fragment(duration * 1000, 28.0, 1),
+ SimTraceWorkload.Fragment(duration * 1000, 3500.0, 1),
+ SimTraceWorkload.Fragment(duration * 1000, 0.0, 1),
+ SimTraceWorkload.Fragment(duration * 1000, 183.0, 1)
+ ),
+ )
+
+ val machine = SimBareMetalMachine(scope, clock, machineModel)
+ val hypervisor = SimFairShareHypervisor(listener)
+
+ launch {
+ machine.run(hypervisor)
+ }
+
+ yield()
+ launch { hypervisor.createMachine(machineModel).run(workloadA) }
+ }
+
+ scope.advanceUntilIdle()
+ scope.uncaughtExceptions.forEach { it.printStackTrace() }
+
+ assertAll(
+ { assertEquals(emptyList<Throwable>(), scope.uncaughtExceptions, "No errors") },
+ { assertEquals(1113300, listener.totalRequestedWork, "Requested Burst does not match") },
+ { assertEquals(1023300, listener.totalGrantedWork, "Granted Burst does not match") },
+ { assertEquals(90000, listener.totalOvercommittedWork, "Overcommissioned Burst does not match") },
+ { assertEquals(1200000, scope.currentTime) }
+ )
+ }
+
+ /**
+ * Test overcommitting of resources via the hypervisor with two VMs.
+ */
+ @Test
+ fun testOvercommittedDual() {
+ val listener = object : SimHypervisor.Listener {
+ var totalRequestedWork = 0L
+ var totalGrantedWork = 0L
+ var totalOvercommittedWork = 0L
+
+ override fun onSliceFinish(
+ hypervisor: SimHypervisor,
+ requestedWork: Long,
+ grantedWork: Long,
+ overcommittedWork: Long,
+ interferedWork: Long,
+ cpuUsage: Double,
+ cpuDemand: Double
+ ) {
+ totalRequestedWork += requestedWork
+ totalGrantedWork += grantedWork
+ totalOvercommittedWork += overcommittedWork
+ }
+ }
+
+ scope.launch {
+ val duration = 5 * 60L
+ val workloadA =
+ SimTraceWorkload(
+ sequenceOf(
+ SimTraceWorkload.Fragment(duration * 1000, 28.0, 1),
+ SimTraceWorkload.Fragment(duration * 1000, 3500.0, 1),
+ SimTraceWorkload.Fragment(duration * 1000, 0.0, 1),
+ SimTraceWorkload.Fragment(duration * 1000, 183.0, 1)
),
)
val workloadB =
SimTraceWorkload(
sequenceOf(
- SimTraceWorkload.Fragment(0, 28L * duration, duration * 1000, 28.0, 2),
- SimTraceWorkload.Fragment(0, 3100L * duration, duration * 1000, 3100.0, 2),
- SimTraceWorkload.Fragment(0, 0, duration * 1000, 0.0, 2),
- SimTraceWorkload.Fragment(0, 73L * duration, duration * 1000, 73.0, 2)
+ SimTraceWorkload.Fragment(duration * 1000, 28.0, 1),
+ SimTraceWorkload.Fragment(duration * 1000, 3100.0, 1),
+ SimTraceWorkload.Fragment(duration * 1000, 0.0, 1),
+ SimTraceWorkload.Fragment(duration * 1000, 73.0, 1)
)
)
val machine = SimBareMetalMachine(scope, clock, machineModel)
- val hypervisor = SimHypervisor(scope, clock, listener)
+ val hypervisor = SimFairShareHypervisor(listener)
launch {
machine.run(hypervisor)
@@ -117,13 +177,14 @@ internal class SimHypervisorTest {
}
scope.advanceUntilIdle()
+ scope.uncaughtExceptions.forEach { it.printStackTrace() }
assertAll(
- { Assertions.assertEquals(emptyList<Throwable>(), scope.uncaughtExceptions, "No errors") },
- { Assertions.assertEquals(2073600, listener.totalRequestedBurst, "Requested Burst does not match") },
- { Assertions.assertEquals(2013600, listener.totalGrantedBurst, "Granted Burst does not match") },
- { Assertions.assertEquals(60000, listener.totalOvercommissionedBurst, "Overcommissioned Burst does not match") },
- { Assertions.assertEquals(1200001, scope.currentTime) }
+ { assertEquals(emptyList<Throwable>(), scope.uncaughtExceptions, "No errors") },
+ { assertEquals(2082000, listener.totalRequestedWork, "Requested Burst does not match") },
+ { assertEquals(1062000, listener.totalGrantedWork, "Granted Burst does not match") },
+ { assertEquals(1020000, listener.totalOvercommittedWork, "Overcommissioned Burst does not match") },
+ { assertEquals(1200000, scope.currentTime) }
)
}
}
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
index 332ca8e9..1036f1ac 100644
--- 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
@@ -23,15 +23,20 @@
package org.opendc.simulator.compute
import kotlinx.coroutines.*
+import kotlinx.coroutines.flow.toList
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.junit.jupiter.api.assertDoesNotThrow
+import org.junit.jupiter.api.assertThrows
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.compute.workload.SimResourceCommand
+import org.opendc.simulator.compute.workload.SimWorkload
import org.opendc.simulator.utils.DelayControllerClockAdapter
/**
@@ -58,7 +63,7 @@ class SimMachineTest {
val machine = SimBareMetalMachine(testScope, clock, machineModel)
testScope.runBlockingTest {
- machine.run(SimFlopsWorkload(2_000, 2, utilization = 1.0))
+ machine.run(SimFlopsWorkload(2_000, utilization = 1.0))
// Two cores execute 1000 MFlOps per second (1000 ms)
assertEquals(1000, testScope.currentTime)
@@ -72,12 +77,83 @@ class SimMachineTest {
val machine = SimBareMetalMachine(testScope, clock, machineModel)
testScope.runBlockingTest {
- machine.run(SimFlopsWorkload(2_000, 2, utilization = 1.0))
- assertEquals(1.0, machine.usage.value)
+ val res = mutableListOf<Double>()
+ val job = launch { machine.usage.toList(res) }
- // Wait for the usage to reset
- delay(1)
- assertEquals(0.0, machine.usage.value)
+ machine.run(SimFlopsWorkload(2_000, utilization = 1.0))
+
+ job.cancel()
+ assertEquals(listOf(0.0, 0.5, 1.0, 0.5, 0.0), res) { "Machine is fully utilized" }
+ }
+ }
+
+ @Test
+ fun testInterrupt() {
+ val testScope = TestCoroutineScope()
+ val clock = DelayControllerClockAdapter(testScope)
+ val machine = SimBareMetalMachine(testScope, clock, machineModel)
+
+ val workload = object : SimWorkload {
+ override fun onStart(ctx: SimExecutionContext) {}
+
+ override fun onStart(ctx: SimExecutionContext, cpu: Int): SimResourceCommand {
+ ctx.interrupt(cpu)
+ return SimResourceCommand.Exit
+ }
+
+ override fun onNext(ctx: SimExecutionContext, cpu: Int, remainingWork: Double): SimResourceCommand {
+ throw IllegalStateException()
+ }
+ }
+
+ assertDoesNotThrow {
+ testScope.runBlockingTest { machine.run(workload) }
+ }
+ }
+
+ @Test
+ fun testExceptionPropagationOnStart() {
+ val testScope = TestCoroutineScope()
+ val clock = DelayControllerClockAdapter(testScope)
+ val machine = SimBareMetalMachine(testScope, clock, machineModel)
+
+ val workload = object : SimWorkload {
+ override fun onStart(ctx: SimExecutionContext) {}
+
+ override fun onStart(ctx: SimExecutionContext, cpu: Int): SimResourceCommand {
+ throw IllegalStateException()
+ }
+
+ override fun onNext(ctx: SimExecutionContext, cpu: Int, remainingWork: Double): SimResourceCommand {
+ throw IllegalStateException()
+ }
+ }
+
+ assertThrows<IllegalStateException> {
+ testScope.runBlockingTest { machine.run(workload) }
+ }
+ }
+
+ @Test
+ fun testExceptionPropagationOnNext() {
+ val testScope = TestCoroutineScope()
+ val clock = DelayControllerClockAdapter(testScope)
+ val machine = SimBareMetalMachine(testScope, clock, machineModel)
+
+ val workload = object : SimWorkload {
+ override fun onStart(ctx: SimExecutionContext) {}
+
+ override fun onStart(ctx: SimExecutionContext, cpu: Int): SimResourceCommand {
+ return SimResourceCommand.Consume(1.0, 1.0)
+ }
+
+ override fun onNext(ctx: SimExecutionContext, cpu: Int, remainingWork: Double): SimResourceCommand {
+ throw IllegalStateException()
+ }
+ }
+
+ assertThrows<IllegalStateException> {
+ testScope.runBlockingTest { machine.run(workload) }
}
}
}
diff --git a/simulator/opendc-simulator/opendc-simulator-compute/src/test/kotlin/org/opendc/simulator/compute/SimSpaceSharedHypervisorTest.kt b/simulator/opendc-simulator/opendc-simulator-compute/src/test/kotlin/org/opendc/simulator/compute/SimSpaceSharedHypervisorTest.kt
new file mode 100644
index 00000000..1a9faf11
--- /dev/null
+++ b/simulator/opendc-simulator/opendc-simulator-compute/src/test/kotlin/org/opendc/simulator/compute/SimSpaceSharedHypervisorTest.kt
@@ -0,0 +1,175 @@
+/*
+ * Copyright (c) 2021 AtLarge Research
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
+ * of this software and associated documentation files (the "Software"), to deal
+ * in the Software without restriction, including without limitation the rights
+ * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+ * copies of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included in all
+ * copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
+ * SOFTWARE.
+ */
+
+package org.opendc.simulator.compute
+
+import kotlinx.coroutines.ExperimentalCoroutinesApi
+import kotlinx.coroutines.flow.toList
+import kotlinx.coroutines.launch
+import kotlinx.coroutines.test.TestCoroutineScope
+import kotlinx.coroutines.yield
+import org.junit.jupiter.api.Assertions.*
+import org.junit.jupiter.api.BeforeEach
+import org.junit.jupiter.api.Test
+import org.junit.jupiter.api.assertThrows
+import org.opendc.simulator.compute.model.MemoryUnit
+import org.opendc.simulator.compute.model.ProcessingNode
+import org.opendc.simulator.compute.model.ProcessingUnit
+import org.opendc.simulator.compute.workload.SimRuntimeWorkload
+import org.opendc.simulator.compute.workload.SimTraceWorkload
+import org.opendc.simulator.utils.DelayControllerClockAdapter
+import java.time.Clock
+
+/**
+ * A test suite for the [SimSpaceSharedHypervisor].
+ */
+@OptIn(ExperimentalCoroutinesApi::class)
+internal class SimSpaceSharedHypervisorTest {
+ private lateinit var scope: TestCoroutineScope
+ private lateinit var clock: Clock
+ private lateinit var machineModel: SimMachineModel
+
+ @BeforeEach
+ fun setUp() {
+ scope = TestCoroutineScope()
+ clock = DelayControllerClockAdapter(scope)
+
+ val cpuNode = ProcessingNode("Intel", "Xeon", "amd64", 1)
+ machineModel = SimMachineModel(
+ cpus = List(cpuNode.coreCount) { ProcessingUnit(cpuNode, it, 3200.0) },
+ memory = List(4) { MemoryUnit("Crucial", "MTA18ASF4G72AZ-3G2B1", 3200.0, 32_000) }
+ )
+ }
+
+ /**
+ * Test a trace workload.
+ */
+ @Test
+ fun testTrace() {
+ val usagePm = mutableListOf<Double>()
+ val usageVm = mutableListOf<Double>()
+
+ scope.launch {
+ val duration = 5 * 60L
+ val workloadA =
+ SimTraceWorkload(
+ sequenceOf(
+ SimTraceWorkload.Fragment(duration * 1000, 28.0, 1),
+ SimTraceWorkload.Fragment(duration * 1000, 3500.0, 1),
+ SimTraceWorkload.Fragment(duration * 1000, 0.0, 1),
+ SimTraceWorkload.Fragment(duration * 1000, 183.0, 1)
+ ),
+ )
+
+ val machine = SimBareMetalMachine(scope, clock, machineModel)
+ val hypervisor = SimSpaceSharedHypervisor()
+
+ launch { machine.usage.toList(usagePm) }
+ launch { machine.run(hypervisor) }
+
+ yield()
+ launch {
+ val vm = hypervisor.createMachine(machineModel)
+ launch { vm.usage.toList(usageVm) }
+ vm.run(workloadA)
+ }
+ }
+
+ scope.advanceUntilIdle()
+
+ assertAll(
+ { assertEquals(listOf(0.0, 0.00875, 1.0, 0.0, 0.0571875, 0.0), usagePm) { "Correct PM usage" } },
+ { assertEquals(listOf(0.0, 0.00875, 1.0, 0.0, 0.0571875, 0.0), usageVm) { "Correct VM usage" } },
+ { assertEquals(5 * 60L * 4000, scope.currentTime) { "Took enough time" } }
+ )
+ }
+
+ /**
+ * Test runtime workload on hypervisor.
+ */
+ @Test
+ fun testRuntimeWorkload() {
+ val duration = 5 * 60L * 1000
+ val workload = SimRuntimeWorkload(duration)
+ val machine = SimBareMetalMachine(scope, clock, machineModel)
+ val hypervisor = SimSpaceSharedHypervisor()
+
+ scope.launch {
+ launch { machine.run(hypervisor) }
+
+ yield()
+ launch { hypervisor.createMachine(machineModel).run(workload) }
+ }
+
+ scope.advanceUntilIdle()
+
+ assertEquals(duration, scope.currentTime) { "Took enough time" }
+ }
+
+ /**
+ * Test concurrent workloads on the machine.
+ */
+ @Test
+ fun testConcurrentWorkloadFails() {
+ val machine = SimBareMetalMachine(scope, clock, machineModel)
+ val hypervisor = SimSpaceSharedHypervisor()
+
+ scope.launch {
+ launch { machine.run(hypervisor) }
+
+ yield()
+
+ hypervisor.createMachine(machineModel)
+
+ assertAll(
+ { assertFalse(hypervisor.canFit(machineModel)) },
+ { assertThrows<IllegalStateException> { hypervisor.createMachine(machineModel) } }
+ )
+ }
+
+ scope.advanceUntilIdle()
+ }
+
+ /**
+ * Test concurrent workloads on the machine.
+ */
+ @Test
+ fun testConcurrentWorkloadSucceeds() {
+ val machine = SimBareMetalMachine(scope, clock, machineModel)
+ val hypervisor = SimSpaceSharedHypervisor()
+
+ scope.launch {
+ launch { machine.run(hypervisor) }
+
+ yield()
+
+ hypervisor.createMachine(machineModel).close()
+
+ assertAll(
+ { assertTrue(hypervisor.canFit(machineModel)) },
+ { assertDoesNotThrow { hypervisor.createMachine(machineModel) } }
+ )
+ }
+
+ scope.advanceUntilIdle()
+ }
+}
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
index 51bed76c..b3e57453 100644
--- 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
@@ -32,42 +32,28 @@ 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)
+ SimFlopsWorkload(-1)
}
}
@Test
fun testUtilizationNonZero() {
assertThrows<IllegalArgumentException>("Utilization cannot be zero") {
- SimFlopsWorkload(1, 1, 0.0)
+ SimFlopsWorkload(1, 0.0)
}
}
@Test
fun testUtilizationPositive() {
assertThrows<IllegalArgumentException>("Utilization cannot be negative") {
- SimFlopsWorkload(1, 1, -1.0)
+ SimFlopsWorkload(1, -1.0)
}
}
@Test
fun testUtilizationNotLargerThanOne() {
assertThrows<IllegalArgumentException>("Utilization cannot be larger than one") {
- SimFlopsWorkload(1, 1, 2.0)
+ SimFlopsWorkload(1, 2.0)
}
}
}
diff --git a/simulator/opendc-trace/build.gradle.kts b/simulator/opendc-trace/build.gradle.kts
new file mode 100644
index 00000000..a1a751a2
--- /dev/null
+++ b/simulator/opendc-trace/build.gradle.kts
@@ -0,0 +1,21 @@
+/*
+ * 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.
+ */
diff --git a/simulator/opendc-compute/opendc-compute-simulator/src/main/kotlin/org/opendc/compute/simulator/SimVirtDriverWorkload.kt b/simulator/opendc-trace/opendc-trace-core/build.gradle.kts
index 58b9408a..3db6669a 100644
--- a/simulator/opendc-compute/opendc-compute-simulator/src/main/kotlin/org/opendc/compute/simulator/SimVirtDriverWorkload.kt
+++ b/simulator/opendc-trace/opendc-trace-core/build.gradle.kts
@@ -20,19 +20,13 @@
* SOFTWARE.
*/
-package org.opendc.compute.simulator
+description = "Event tracing library for OpenDC"
-import kotlinx.coroutines.coroutineScope
-import org.opendc.simulator.compute.SimExecutionContext
-import org.opendc.simulator.compute.workload.SimWorkload
-
-public class SimVirtDriverWorkload : SimWorkload {
- public lateinit var driver: SimVirtDriver
+/* Build configuration */
+plugins {
+ `kotlin-library-convention`
+}
- override suspend fun run(ctx: SimExecutionContext) {
- coroutineScope {
- driver = SimVirtDriver(this, ctx.clock, ctx)
- driver.run()
- }
- }
+dependencies {
+ api("org.jetbrains.kotlinx:kotlinx-coroutines-core:${Library.KOTLINX_COROUTINES}")
}
diff --git a/simulator/opendc-workflows/src/main/kotlin/org/opendc/workflows/service/stage/resource/FirstFitResourceSelectionPolicy.kt b/simulator/opendc-trace/opendc-trace-core/src/main/kotlin/org/opendc/trace/core/Event.kt
index 8dc323ec..1f4bb267 100644
--- a/simulator/opendc-workflows/src/main/kotlin/org/opendc/workflows/service/stage/resource/FirstFitResourceSelectionPolicy.kt
+++ b/simulator/opendc-trace/opendc-trace-core/src/main/kotlin/org/opendc/trace/core/Event.kt
@@ -20,17 +20,15 @@
* SOFTWARE.
*/
-package org.opendc.workflows.service.stage.resource
-
-import org.opendc.compute.core.metal.Node
-import org.opendc.workflows.service.StageWorkflowService
+package org.opendc.trace.core
/**
- * A [ResourceSelectionPolicy] that selects the first machine that is available.
+ * Base class for events reported by the OpenDC tracing library.
*/
-public object FirstFitResourceSelectionPolicy : ResourceSelectionPolicy {
- override fun invoke(scheduler: StageWorkflowService): Comparator<Node> =
- Comparator<Node> { _, _ -> 1 }
-
- override fun toString(): String = "First-Fit"
+public abstract class Event(timestamp: Long = Long.MIN_VALUE) {
+ /**
+ * The timestamp at which the event has occurred.
+ */
+ public var timestamp: Long = timestamp
+ internal set
}
diff --git a/simulator/opendc-trace/opendc-trace-core/src/main/kotlin/org/opendc/trace/core/EventStream.kt b/simulator/opendc-trace/opendc-trace-core/src/main/kotlin/org/opendc/trace/core/EventStream.kt
new file mode 100644
index 00000000..ac2b5e9b
--- /dev/null
+++ b/simulator/opendc-trace/opendc-trace-core/src/main/kotlin/org/opendc/trace/core/EventStream.kt
@@ -0,0 +1,76 @@
+/*
+ * 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.trace.core
+
+/**
+ * A stream of [Event]s.
+ */
+public interface EventStream : AutoCloseable {
+ /**
+ * Register the specified [action] to be performed on every event in the stream.
+ */
+ public fun onEvent(action: (Event) -> Unit)
+
+ /**
+ * Register the specified [action] to be performed on events of type [E].
+ */
+ public fun <E : Event> onEvent(type: Class<E>, action: (E) -> Unit)
+
+ /**
+ * Register the specified [action] to be performed on errors.
+ */
+ public fun onError(action: (Throwable) -> Unit)
+
+ /**
+ * Register the specified [action] to be performed when the stream is closed.
+ */
+ public fun onClose(action: Runnable)
+
+ /**
+ * Unregister the specified [action].
+ *
+ * @return `true` if an action was unregistered, `false` otherwise.
+ */
+ public fun remove(action: Any): Boolean
+
+ /**
+ * Start the processing of events in the current coroutine.
+ *
+ * @throws IllegalStateException if the stream was already started.
+ */
+ public suspend fun start()
+
+ /**
+ * Release all resources associated with this stream.
+ *
+ * @throws IllegalStateException if the stream was already stopped.
+ */
+ public override fun close()
+}
+
+/**
+ * Register the specified [action] to be performed on events of type [E].
+ */
+public inline fun <reified E : Event> EventStream.onEvent(noinline action: (E) -> Unit) {
+ onEvent(E::class.java, action)
+}
diff --git a/simulator/opendc-trace/opendc-trace-core/src/main/kotlin/org/opendc/trace/core/EventTracer.kt b/simulator/opendc-trace/opendc-trace-core/src/main/kotlin/org/opendc/trace/core/EventTracer.kt
new file mode 100644
index 00000000..4f978f4f
--- /dev/null
+++ b/simulator/opendc-trace/opendc-trace-core/src/main/kotlin/org/opendc/trace/core/EventTracer.kt
@@ -0,0 +1,84 @@
+/*
+ * 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.trace.core
+
+import org.opendc.trace.core.internal.EventTracerImpl
+import java.time.Clock
+
+/**
+ * An [EventTracer] is responsible for recording the events that occur in a system.
+ */
+public interface EventTracer : AutoCloseable {
+ /**
+ * The [Clock] used to measure the timestamp and duration of the events.
+ */
+ public val clock: Clock
+
+ /**
+ * Determine whether the specified [Event] class is currently enabled in any of the active recordings.
+ *
+ * @return `true` if the event is enabled, `false` otherwise.
+ */
+ public fun isEnabled(type: Class<out Event>): Boolean
+
+ /**
+ * Commit the specified [event] to the appropriate event streams.
+ */
+ public fun commit(event: Event)
+
+ /**
+ * Create a new [RecordingStream] which is able to actively capture events emitted to the [EventTracer].
+ */
+ public fun openRecording(): RecordingStream
+
+ /**
+ * Terminate the lifecycle of the [EventTracer] and close its associated event streams.
+ */
+ public override fun close()
+
+ public companion object {
+ /**
+ * Construct a new [EventTracer] instance.
+ *
+ * @param clock The [Clock] used to measure the timestamps.
+ */
+ @JvmName("create")
+ public operator fun invoke(clock: Clock): EventTracer = EventTracerImpl(clock)
+ }
+}
+
+/**
+ * Determine whether the [Event] of type [E] is currently enabled in any of the active recordings.
+ *
+ * @return `true` if the event is enabled, `false` otherwise.
+ */
+public inline fun <reified E : Event> EventTracer.isEnabled(): Boolean = isEnabled(E::class.java)
+
+/**
+ * Lazily construct an [Event] of type [E] if it is enabled and commit it to the appropriate event streams.
+ */
+public inline fun <reified E : Event> EventTracer.commit(block: () -> E) {
+ if (isEnabled<E>()) {
+ commit(block())
+ }
+}
diff --git a/simulator/opendc-trace/opendc-trace-core/src/main/kotlin/org/opendc/trace/core/Extensions.kt b/simulator/opendc-trace/opendc-trace-core/src/main/kotlin/org/opendc/trace/core/Extensions.kt
new file mode 100644
index 00000000..84dcc61a
--- /dev/null
+++ b/simulator/opendc-trace/opendc-trace-core/src/main/kotlin/org/opendc/trace/core/Extensions.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.trace.core
+
+import kotlinx.coroutines.CancellationException
+import kotlinx.coroutines.ExperimentalCoroutinesApi
+import kotlinx.coroutines.cancel
+import kotlinx.coroutines.channels.awaitClose
+import kotlinx.coroutines.channels.sendBlocking
+import kotlinx.coroutines.flow.Flow
+import kotlinx.coroutines.flow.callbackFlow
+
+/**
+ * Convert an [EventStream] to a [Flow] of [Event]s but do not start collection of the stream.
+ */
+@OptIn(ExperimentalCoroutinesApi::class)
+public fun EventStream.asFlow(): Flow<Event> = callbackFlow {
+ onEvent { sendBlocking(it) }
+ onError { cancel(CancellationException("API error", it)) }
+ onClose { channel.close() }
+ awaitClose { this@asFlow.close() }
+}
+
+/**
+ * Convert an [EventStream] to a [Flow] of [Event]s but do not start collection of the stream.
+ */
+@OptIn(ExperimentalCoroutinesApi::class)
+public fun EventStream.consumeAsFlow(): Flow<Event> = callbackFlow {
+ onEvent { sendBlocking(it) }
+ onError { cancel(CancellationException("API error", it)) }
+ start()
+}
+
+/**
+ * Convert an [EventStream] to a [Flow] of [Event] of type [E] but do not start collection of the stream.
+ */
+@OptIn(ExperimentalCoroutinesApi::class)
+public inline fun <reified E : Event> EventStream.asTypedFlow(): Flow<E> = callbackFlow {
+ onEvent<E> { sendBlocking(it) }
+ onError { cancel(CancellationException("API error", it)) }
+ onClose { channel.close() }
+ awaitClose { this@asTypedFlow.close() }
+}
+
+/**
+ * Convert an [EventStream] to a [Flow] of [Event] of type [E] but do not start collection of the stream.
+ */
+@OptIn(ExperimentalCoroutinesApi::class)
+public inline fun <reified E : Event> EventStream.consumeAsTypedFlow(): Flow<E> = callbackFlow {
+ onEvent<E> { sendBlocking(it) }
+ onError { cancel(CancellationException("API error", it)) }
+ start()
+}
diff --git a/simulator/opendc-experiments/opendc-experiments-sc20/src/main/kotlin/org/opendc/experiments/sc20/runner/ExperimentRunner.kt b/simulator/opendc-trace/opendc-trace-core/src/main/kotlin/org/opendc/trace/core/RecordingStream.kt
index a59481c0..f49e7c49 100644
--- a/simulator/opendc-experiments/opendc-experiments-sc20/src/main/kotlin/org/opendc/experiments/sc20/runner/ExperimentRunner.kt
+++ b/simulator/opendc-trace/opendc-trace-core/src/main/kotlin/org/opendc/trace/core/RecordingStream.kt
@@ -20,30 +20,33 @@
* SOFTWARE.
*/
-package org.opendc.experiments.sc20.runner
-
-import org.opendc.experiments.sc20.runner.execution.ExperimentExecutionListener
+package org.opendc.trace.core
/**
- * An [ExperimentRunner] facilitates discovery and execution of experiments.
+ * A recording stream that produces events from an [EventTracer].
*/
-public interface ExperimentRunner {
+public interface RecordingStream : EventStream {
/**
- * The unique identifier of this runner.
+ * Enable recording of the specified event [type].
*/
- public val id: String
+ public fun enable(type: Class<out Event>)
/**
- * The version of this runner.
+ * Disable recording of the specified event [type]
*/
- public val version: String?
- get() = null
+ public fun disable(type: Class<out Event>)
+}
- /**
- * Execute the specified experiment represented as [ExperimentDescriptor].
- *
- * @param root The experiment to execute.
- * @param listener The listener to report events to.
- */
- public fun execute(root: ExperimentDescriptor, listener: ExperimentExecutionListener)
+/**
+ * Enable recording of events of type [E].
+ */
+public inline fun <reified E : Event> RecordingStream.enable() {
+ enable(E::class.java)
+}
+
+/**
+ * Disable recording of events of type [E].
+ */
+public inline fun <reified E : Event> RecordingStream.disable() {
+ enable(E::class.java)
}
diff --git a/simulator/opendc-trace/opendc-trace-core/src/main/kotlin/org/opendc/trace/core/internal/AbstractEventStream.kt b/simulator/opendc-trace/opendc-trace-core/src/main/kotlin/org/opendc/trace/core/internal/AbstractEventStream.kt
new file mode 100644
index 00000000..fac99664
--- /dev/null
+++ b/simulator/opendc-trace/opendc-trace-core/src/main/kotlin/org/opendc/trace/core/internal/AbstractEventStream.kt
@@ -0,0 +1,139 @@
+/*
+ * 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.trace.core.internal
+
+import kotlinx.coroutines.suspendCancellableCoroutine
+import org.opendc.trace.core.Event
+import org.opendc.trace.core.EventStream
+import kotlin.coroutines.Continuation
+import kotlin.coroutines.resume
+
+/**
+ * Base implementation of the [EventStream] implementation.
+ */
+internal abstract class AbstractEventStream : EventStream {
+ /**
+ * The state of the stream.
+ */
+ protected var state = StreamState.Pending
+
+ /**
+ * The event actions to dispatch to.
+ */
+ private val eventActions = mutableListOf<EventDispatcher>()
+
+ /**
+ * The error actions to use.
+ */
+ private val errorActions = mutableListOf<(Throwable) -> Unit>()
+
+ /**
+ * The close actions to use.
+ */
+ private val closeActions = mutableListOf<Runnable>()
+
+ /**
+ * The continuation that is invoked when the stream closes.
+ */
+ private var cont: Continuation<Unit>? = null
+
+ /**
+ * Dispatch the specified [event] to this stream.
+ */
+ fun dispatch(event: Event) {
+ val actions = eventActions
+
+ // TODO Opportunity for further optimizations if needed (e.g. dispatch based on event type)
+ for (action in actions) {
+ if (!action.accepts(event)) {
+ continue
+ }
+
+ try {
+ action(event)
+ } catch (e: Exception) {
+ handleError(e)
+ }
+ }
+ }
+
+ /**
+ * Handle the specified [throwable] that occurred while dispatching an event.
+ */
+ private fun handleError(throwable: Throwable) {
+ val actions = errorActions
+
+ // Default exception handler
+ if (actions.isEmpty()) {
+ throwable.printStackTrace()
+ return
+ }
+
+ for (action in actions) {
+ action(throwable)
+ }
+ }
+
+ override fun onEvent(action: (Event) -> Unit) {
+ eventActions += EventDispatcher(null, action)
+ }
+
+ override fun <E : Event> onEvent(type: Class<E>, action: (E) -> Unit) {
+ @Suppress("UNCHECKED_CAST") // This cast must succeed
+ eventActions += EventDispatcher(type, action as (Event) -> Unit)
+ }
+
+ override fun onError(action: (Throwable) -> Unit) {
+ errorActions += action
+ }
+
+ override fun onClose(action: Runnable) {
+ closeActions += action
+ }
+
+ override fun remove(action: Any): Boolean {
+ return eventActions.removeIf { it.action == action } || errorActions.remove(action) || closeActions.remove(action)
+ }
+
+ override suspend fun start() {
+ check(state == StreamState.Pending) { "Stream has already started/closed" }
+
+ state = StreamState.Started
+
+ return suspendCancellableCoroutine { cont -> this.cont = cont }
+ }
+
+ override fun close() {
+ if (state == StreamState.Closed) {
+ return
+ }
+
+ state = StreamState.Closed
+ cont?.resume(Unit)
+
+ val actions = closeActions
+ for (action in actions) {
+ action.run()
+ }
+ }
+}
diff --git a/simulator/opendc-trace/opendc-trace-core/src/main/kotlin/org/opendc/trace/core/internal/Dispatcher.kt b/simulator/opendc-trace/opendc-trace-core/src/main/kotlin/org/opendc/trace/core/internal/Dispatcher.kt
new file mode 100644
index 00000000..8b6de75e
--- /dev/null
+++ b/simulator/opendc-trace/opendc-trace-core/src/main/kotlin/org/opendc/trace/core/internal/Dispatcher.kt
@@ -0,0 +1,77 @@
+/*
+ * 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.trace.core.internal
+
+import org.opendc.trace.core.Event
+
+/**
+ * The [Dispatcher] is responsible for dispatching events onto configured actions.
+ */
+internal class Dispatcher {
+ /**
+ * The event actions to dispatch to.
+ */
+ private val eventActions = mutableListOf<EventDispatcher>()
+
+ /**
+ * The error actions to use.
+ */
+ private val errorActions = mutableListOf<(Throwable) -> Unit>()
+
+ /**
+ * Dispatch the specified [event].
+ */
+ fun dispatch(event: Event) {
+ val actions = eventActions
+
+ // TODO Opportunity for further optimizations if needed (e.g. dispatch based on event type)
+ for (action in actions) {
+ if (!action.accepts(event)) {
+ continue
+ }
+
+ try {
+ action(event)
+ } catch (e: Exception) {
+ handleError(e)
+ }
+ }
+ }
+
+ /**
+ * Handle the specified [throwable] that occurred while dispatching an event.
+ */
+ private fun handleError(throwable: Throwable) {
+ val actions = errorActions
+
+ // Default exception handler
+ if (actions.isEmpty()) {
+ throwable.printStackTrace()
+ return
+ }
+
+ for (action in actions) {
+ action(throwable)
+ }
+ }
+}
diff --git a/simulator/opendc-experiments/opendc-experiments-sc20/src/main/kotlin/org/opendc/experiments/sc20/runner/execution/ExperimentExecutionContext.kt b/simulator/opendc-trace/opendc-trace-core/src/main/kotlin/org/opendc/trace/core/internal/EventDispatcher.kt
index 942eb891..b2a662eb 100644
--- a/simulator/opendc-experiments/opendc-experiments-sc20/src/main/kotlin/org/opendc/experiments/sc20/runner/execution/ExperimentExecutionContext.kt
+++ b/simulator/opendc-trace/opendc-trace-core/src/main/kotlin/org/opendc/trace/core/internal/EventDispatcher.kt
@@ -1,7 +1,5 @@
/*
- * MIT License
- *
- * Copyright (c) 2020 atlarge-research
+ * 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
@@ -22,24 +20,25 @@
* SOFTWARE.
*/
-package org.opendc.experiments.sc20.runner.execution
+package org.opendc.trace.core.internal
+
+import org.opendc.trace.core.Event
/**
- * The execution context of an experiment.
+ * A dispatcher responsible for conditionally dispatching an event.
*/
-public interface ExperimentExecutionContext {
- /**
- * The execution listener to use.
- */
- public val listener: ExperimentExecutionListener
-
+internal class EventDispatcher(val type: Class<out Event>?, val action: (Event) -> Unit) {
/**
- * The experiment scheduler to use.
+ * Determine whether this dispatcher accepts the specified event.
*/
- public val scheduler: ExperimentScheduler
+ fun accepts(event: Event): Boolean {
+ return type == null || type.isAssignableFrom(event.javaClass)
+ }
/**
- * A cache for objects within a single runner.
+ * Invoke the specified [event] on this action.
*/
- public val cache: MutableMap<Any?, Any?>
+ operator fun invoke(event: Event) {
+ action(event)
+ }
}
diff --git a/simulator/opendc-trace/opendc-trace-core/src/main/kotlin/org/opendc/trace/core/internal/EventTracerImpl.kt b/simulator/opendc-trace/opendc-trace-core/src/main/kotlin/org/opendc/trace/core/internal/EventTracerImpl.kt
new file mode 100644
index 00000000..e85d0779
--- /dev/null
+++ b/simulator/opendc-trace/opendc-trace-core/src/main/kotlin/org/opendc/trace/core/internal/EventTracerImpl.kt
@@ -0,0 +1,157 @@
+/*
+ * 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.trace.core.internal
+
+import org.opendc.trace.core.Event
+import org.opendc.trace.core.EventTracer
+import org.opendc.trace.core.RecordingStream
+import java.lang.reflect.Modifier
+import java.time.Clock
+import java.util.*
+
+/**
+ * Default implementation of the [EventTracer] interface.
+ */
+internal class EventTracerImpl(override val clock: Clock) : EventTracer {
+ /**
+ * The set of enabled events.
+ */
+ private val enabledEvents = IdentityHashMap<Class<out Event>, MutableList<Stream>>()
+
+ /**
+ * The event streams created by the tracer.
+ */
+ private val streams = WeakHashMap<Stream, Unit>()
+
+ /**
+ * A flag to indicate that the stream is closed.
+ */
+ private var isClosed: Boolean = false
+
+ override fun isEnabled(type: Class<out Event>): Boolean = enabledEvents.containsKey(type)
+
+ override fun commit(event: Event) {
+ val type = event.javaClass
+
+ // Assign timestamp if not set
+ if (event.timestamp == Long.MIN_VALUE) {
+ event.timestamp = clock.millis()
+ }
+
+ if (!isEnabled(type) || isClosed) {
+ return
+ }
+
+ val streams = enabledEvents[type] ?: return
+ for (stream in streams) {
+ stream.dispatch(event)
+ }
+ }
+
+ override fun openRecording(): RecordingStream = Stream()
+
+ override fun close() {
+ isClosed = true
+
+ val streams = streams
+ for ((stream, _) in streams) {
+ stream.close()
+ }
+
+ enabledEvents.clear()
+ }
+
+ /**
+ * Enable the specified [type] for the given [stream].
+ */
+ private fun enableFor(type: Class<out Event>, stream: Stream) {
+ val res = enabledEvents.computeIfAbsent(type) { mutableListOf() }
+ res.add(stream)
+ }
+
+ /**
+ * Disable the specified [type] for the given [stream].
+ */
+ private fun disableFor(type: Class<out Event>, stream: Stream) {
+ enabledEvents[type]?.remove(stream)
+ }
+
+ /**
+ * The [RecordingStream] associated with this [EventTracer] implementation.
+ */
+ private inner class Stream : AbstractEventStream(), RecordingStream {
+ /**
+ * The set of enabled events for this stream.
+ */
+ private val enabledEvents = IdentityHashMap<Class<out Event>, Unit>()
+
+ init {
+ streams[this] = Unit
+ }
+
+ override fun enable(type: Class<out Event>) {
+ validateEventClass(type)
+
+ if (enabledEvents.put(type, Unit) == null && state == StreamState.Started) {
+ enableFor(type, this)
+ }
+ }
+
+ override fun disable(type: Class<out Event>) {
+ validateEventClass(type)
+
+ if (enabledEvents.remove(type) != null && state == StreamState.Started) {
+ disableFor(type, this)
+ }
+ }
+
+ override suspend fun start() {
+ val enabledEvents = enabledEvents
+ for ((event, _) in enabledEvents) {
+ enableFor(event, this)
+ }
+
+ super.start()
+ }
+
+ override fun close() {
+ val enabledEvents = enabledEvents
+ for ((event, _) in enabledEvents) {
+ disableFor(event, this)
+ }
+
+ // Remove this stream from the active streams
+ streams.remove(this)
+
+ super.close()
+ }
+
+ /**
+ * Validate the specified event subclass.
+ */
+ private fun validateEventClass(type: Class<out Event>) {
+ require(!Modifier.isAbstract(type.modifiers)) { "Abstract event classes are not allowed" }
+ require(Event::class.java.isAssignableFrom(type)) { "Must be subclass to ${Event::class.qualifiedName}" }
+ }
+ }
+}
diff --git a/simulator/opendc-experiments/opendc-experiments-sc20/src/main/kotlin/org/opendc/experiments/sc20/runner/TrialExperimentDescriptor.kt b/simulator/opendc-trace/opendc-trace-core/src/main/kotlin/org/opendc/trace/core/internal/StreamState.kt
index abc52997..9f411e0d 100644
--- a/simulator/opendc-experiments/opendc-experiments-sc20/src/main/kotlin/org/opendc/experiments/sc20/runner/TrialExperimentDescriptor.kt
+++ b/simulator/opendc-trace/opendc-trace-core/src/main/kotlin/org/opendc/trace/core/internal/StreamState.kt
@@ -1,7 +1,5 @@
/*
- * MIT License
- *
- * Copyright (c) 2020 atlarge-research
+ * 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
@@ -22,11 +20,11 @@
* SOFTWARE.
*/
-package org.opendc.experiments.sc20.runner
+package org.opendc.trace.core.internal
/**
- * An abstract [ExperimentDescriptor] specifically for trials.
+ * The state of a [Stream].
*/
-public abstract class TrialExperimentDescriptor : ExperimentDescriptor() {
- override val type: Type = Type.TRIAL
+internal enum class StreamState {
+ Pending, Started, Closed
}
diff --git a/simulator/opendc-utils/build.gradle.kts b/simulator/opendc-utils/build.gradle.kts
index d66148c4..d4b8c514 100644
--- a/simulator/opendc-utils/build.gradle.kts
+++ b/simulator/opendc-utils/build.gradle.kts
@@ -29,4 +29,9 @@ plugins {
dependencies {
api("org.jetbrains.kotlinx:kotlinx-coroutines-core:${Library.KOTLINX_COROUTINES}")
+
+ testImplementation(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-utils/src/main/kotlin/org/opendc/utils/TimerScheduler.kt b/simulator/opendc-utils/src/main/kotlin/org/opendc/utils/TimerScheduler.kt
new file mode 100644
index 00000000..ff116443
--- /dev/null
+++ b/simulator/opendc-utils/src/main/kotlin/org/opendc/utils/TimerScheduler.kt
@@ -0,0 +1,209 @@
+/*
+ * 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.utils
+
+import kotlinx.coroutines.CoroutineScope
+import kotlinx.coroutines.ExperimentalCoroutinesApi
+import kotlinx.coroutines.channels.Channel
+import kotlinx.coroutines.channels.sendBlocking
+import kotlinx.coroutines.launch
+import kotlinx.coroutines.selects.select
+import java.time.Clock
+import java.util.*
+import kotlin.math.max
+
+/**
+ * A TimerScheduler facilitates scheduled execution of future tasks.
+ *
+ * @property coroutineScope The [CoroutineScope] to run the tasks in.
+ * @property clock The clock to keep track of the time.
+ */
+@OptIn(ExperimentalCoroutinesApi::class)
+public class TimerScheduler<T>(private val coroutineScope: CoroutineScope, private val clock: Clock) : AutoCloseable {
+ /**
+ * A priority queue containing the tasks to be scheduled in the future.
+ */
+ private val queue = PriorityQueue<Timer>()
+
+ /**
+ * A map that keeps track of the timers.
+ */
+ private val timers = mutableMapOf<T, Timer>()
+
+ /**
+ * The channel to communicate with the
+ */
+ private val channel = Channel<Long?>(Channel.CONFLATED)
+
+ /**
+ * The scheduling job.
+ */
+ private val job = coroutineScope.launch {
+ val queue = queue
+ var next: Long? = channel.receive()
+
+ while (true) {
+ next = select {
+ channel.onReceive { it }
+
+ val delay = next?.let { max(0L, it - clock.millis()) } ?: return@select
+
+ onTimeout(delay) {
+ while (queue.isNotEmpty()) {
+ val timer = queue.peek()
+ val timestamp = clock.millis()
+
+ assert(timer.timestamp >= timestamp) { "Found task in the past" }
+
+ if (timer.timestamp > timestamp && !timer.isCancelled) {
+ // Schedule a task for the next event to occur.
+ return@onTimeout timer.timestamp
+ }
+
+ queue.poll()
+
+ if (!timer.isCancelled) {
+ timers.remove(timer.key)
+ timer()
+ }
+ }
+
+ null
+ }
+ }
+ }
+ }
+
+ /**
+ * Stop the scheduler.
+ */
+ override fun close() {
+ cancelAll()
+ job.cancel()
+ }
+
+ /**
+ * Cancel a timer with a given key.
+ *
+ * If canceling a timer that was already canceled, or key never was used to start
+ * a timer this operation will do nothing.
+ *
+ * @param key The key of the timer to cancel.
+ */
+ public fun cancel(key: T) {
+ if (!job.isActive) {
+ return
+ }
+
+ val timer = timers.remove(key)
+
+ // Mark the timer as cancelled
+ timer?.isCancelled = true
+
+ // Optimization: check whether we are the head of the queue
+ if (queue.peek() == timer) {
+ queue.poll()
+
+ if (queue.isNotEmpty()) {
+ channel.sendBlocking(queue.peek().timestamp)
+ } else {
+ channel.sendBlocking(null)
+ }
+ }
+ }
+
+ /**
+ * Cancel all timers.
+ */
+ public fun cancelAll() {
+ queue.clear()
+ timers.clear()
+ }
+
+ /**
+ * Check if a timer with a given key is active.
+ *
+ * @param key The key to check if active.
+ * @return `true` if the timer with the specified [key] is active, `false` otherwise.
+ */
+ public fun isTimerActive(key: T): Boolean = key in timers
+
+ /**
+ * Start a timer that will invoke the specified [block] after [delay].
+ *
+ * Each timer has a key and if a new timer with same key is started the previous is cancelled.
+ *
+ * @param key The key of the timer to start.
+ * @param delay The delay before invoking the block.
+ * @param block The block to invoke.
+ */
+ public fun startSingleTimer(key: T, delay: Long, block: () -> Unit) {
+ startSingleTimerTo(key, clock.millis() + delay, block)
+ }
+
+ /**
+ * Start a timer that will invoke the specified [block] at [timestamp].
+ *
+ * Each timer has a key and if a new timer with same key is started the previous is cancelled.
+ *
+ * @param key The key of the timer to start.
+ * @param timestamp The timestamp at which to invoke the block.
+ * @param block The block to invoke.
+ */
+ public fun startSingleTimerTo(key: T, timestamp: Long, block: () -> Unit) {
+ val now = clock.millis()
+
+ require(timestamp >= now) { "Timestamp must be in the future" }
+ check(job.isActive) { "Timer is stopped" }
+
+ val timer = Timer(key, timestamp, block)
+
+ timers.compute(key) { _, old ->
+ old?.isCancelled = true
+ timer
+ }
+ queue.add(timer)
+
+ // Check if we need to push the interruption forward
+ if (queue.peek() == timer) {
+ channel.sendBlocking(timer.timestamp)
+ }
+ }
+
+ /**
+ * A task that is scheduled to run in the future.
+ */
+ private inner class Timer(val key: T, val timestamp: Long, val block: () -> Unit) : Comparable<Timer> {
+ /**
+ * A flag to indicate that the task has been cancelled.
+ */
+ var isCancelled: Boolean = false
+
+ /**
+ * Run the task.
+ */
+ operator fun invoke(): Unit = block()
+
+ override fun compareTo(other: Timer): Int = timestamp.compareTo(other.timestamp)
+ }
+}
diff --git a/simulator/opendc-utils/src/test/kotlin/org/opendc/utils/TimerSchedulerTest.kt b/simulator/opendc-utils/src/test/kotlin/org/opendc/utils/TimerSchedulerTest.kt
new file mode 100644
index 00000000..3a4acc90
--- /dev/null
+++ b/simulator/opendc-utils/src/test/kotlin/org/opendc/utils/TimerSchedulerTest.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.utils
+
+import kotlinx.coroutines.ExperimentalCoroutinesApi
+import kotlinx.coroutines.test.runBlockingTest
+import org.junit.jupiter.api.Assertions.*
+import org.junit.jupiter.api.Test
+import org.junit.jupiter.api.assertThrows
+import org.opendc.simulator.utils.DelayControllerClockAdapter
+
+/**
+ * A test suite for the [TimerScheduler] class.
+ */
+@OptIn(ExperimentalCoroutinesApi::class)
+internal class TimerSchedulerTest {
+ @Test
+ fun testBasicTimer() {
+ runBlockingTest {
+ val clock = DelayControllerClockAdapter(this)
+ val scheduler = TimerScheduler<Int>(this, clock)
+
+ scheduler.startSingleTimer(0, 1000) {
+ scheduler.close()
+ assertEquals(1000, clock.millis())
+ }
+ }
+ }
+
+ @Test
+ fun testCancelNonExisting() {
+ runBlockingTest {
+ val clock = DelayControllerClockAdapter(this)
+ val scheduler = TimerScheduler<Int>(this, clock)
+
+ scheduler.cancel(1)
+ scheduler.close()
+ }
+ }
+
+ @Test
+ fun testCancelExisting() {
+ runBlockingTest {
+ val clock = DelayControllerClockAdapter(this)
+ val scheduler = TimerScheduler<Int>(this, clock)
+
+ scheduler.startSingleTimer(0, 1000) {
+ assertFalse(false)
+ }
+
+ scheduler.startSingleTimer(1, 100) {
+ scheduler.cancel(0)
+ scheduler.close()
+
+ assertEquals(100, clock.millis())
+ }
+ }
+ }
+
+ @Test
+ fun testCancelAll() {
+ runBlockingTest {
+ val clock = DelayControllerClockAdapter(this)
+ val scheduler = TimerScheduler<Int>(this, clock)
+
+ scheduler.startSingleTimer(0, 1000) {
+ assertFalse(false)
+ }
+
+ scheduler.startSingleTimer(1, 100) {
+ assertFalse(false)
+ }
+
+ scheduler.close()
+ }
+ }
+
+ @Test
+ fun testOverride() {
+ runBlockingTest {
+ val clock = DelayControllerClockAdapter(this)
+ val scheduler = TimerScheduler<Int>(this, clock)
+
+ scheduler.startSingleTimer(0, 1000) {
+ assertFalse(false)
+ }
+
+ scheduler.startSingleTimer(0, 200) {
+ scheduler.close()
+
+ assertEquals(200, clock.millis())
+ }
+ }
+ }
+
+ @Test
+ fun testStopped() {
+ runBlockingTest {
+ val clock = DelayControllerClockAdapter(this)
+ val scheduler = TimerScheduler<Int>(this, clock)
+
+ scheduler.close()
+
+ assertThrows<IllegalStateException> {
+ scheduler.startSingleTimer(1, 100) {
+ assertFalse(false)
+ }
+ }
+ }
+ }
+
+ @Test
+ fun testNegativeDelay() {
+ runBlockingTest {
+ val clock = DelayControllerClockAdapter(this)
+ val scheduler = TimerScheduler<Int>(this, clock)
+
+ assertThrows<IllegalArgumentException> {
+ scheduler.startSingleTimer(1, -1) {
+ assertFalse(false)
+ }
+ }
+
+ scheduler.close()
+ }
+ }
+}
diff --git a/simulator/opendc-workflows/build.gradle.kts b/simulator/opendc-workflows/build.gradle.kts
index f61bdac6..e9c85de5 100644
--- a/simulator/opendc-workflows/build.gradle.kts
+++ b/simulator/opendc-workflows/build.gradle.kts
@@ -30,14 +30,18 @@ plugins {
dependencies {
api(project(":opendc-core"))
api(project(":opendc-compute:opendc-compute-core"))
+ api(project(":opendc-trace:opendc-trace-core"))
implementation(project(":opendc-utils"))
+ implementation("io.github.microutils:kotlin-logging:1.7.9")
testImplementation(project(":opendc-simulator:opendc-simulator-core"))
+ testImplementation(project(":opendc-compute:opendc-compute-simulator"))
testImplementation(project(":opendc-format"))
testImplementation("com.fasterxml.jackson.module:jackson-module-kotlin:2.9.8") {
exclude("org.jetbrains.kotlin", module = "kotlin-reflect")
}
testImplementation(kotlin("reflect"))
+ 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}")
testImplementation("org.junit.platform:junit-platform-launcher:${Library.JUNIT_PLATFORM}")
diff --git a/simulator/opendc-workflows/src/main/kotlin/org/opendc/workflows/service/StageWorkflowService.kt b/simulator/opendc-workflows/src/main/kotlin/org/opendc/workflows/service/StageWorkflowService.kt
index 3b4e6eab..e04c8a4c 100644
--- a/simulator/opendc-workflows/src/main/kotlin/org/opendc/workflows/service/StageWorkflowService.kt
+++ b/simulator/opendc-workflows/src/main/kotlin/org/opendc/workflows/service/StageWorkflowService.kt
@@ -26,40 +26,46 @@ import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.ExperimentalCoroutinesApi
import kotlinx.coroutines.flow.Flow
import kotlinx.coroutines.flow.launchIn
+import kotlinx.coroutines.flow.map
import kotlinx.coroutines.flow.onEach
import kotlinx.coroutines.launch
+import mu.KotlinLogging
+import org.opendc.compute.core.Flavor
import org.opendc.compute.core.Server
import org.opendc.compute.core.ServerEvent
import org.opendc.compute.core.ServerState
-import org.opendc.compute.core.metal.Node
-import org.opendc.compute.core.metal.service.ProvisioningService
-import org.opendc.utils.flow.EventFlow
+import org.opendc.compute.core.virt.service.VirtProvisioningService
+import org.opendc.trace.core.EventTracer
+import org.opendc.trace.core.consumeAsFlow
+import org.opendc.trace.core.enable
import org.opendc.workflows.service.stage.job.JobAdmissionPolicy
import org.opendc.workflows.service.stage.job.JobOrderPolicy
-import org.opendc.workflows.service.stage.resource.ResourceFilterPolicy
-import org.opendc.workflows.service.stage.resource.ResourceSelectionPolicy
import org.opendc.workflows.service.stage.task.TaskEligibilityPolicy
import org.opendc.workflows.service.stage.task.TaskOrderPolicy
import org.opendc.workflows.workload.Job
+import org.opendc.workflows.workload.WORKFLOW_TASK_CORES
import java.time.Clock
import java.util.*
/**
* A [WorkflowService] that distributes work through a multi-stage process based on the Reference Architecture for
- * Topology Scheduling.
+ * Datacenter Scheduling.
*/
public class StageWorkflowService(
internal val coroutineScope: CoroutineScope,
internal val clock: Clock,
- private val provisioningService: ProvisioningService,
+ internal val tracer: EventTracer,
+ private val provisioningService: VirtProvisioningService,
mode: WorkflowSchedulerMode,
jobAdmissionPolicy: JobAdmissionPolicy,
jobOrderPolicy: JobOrderPolicy,
taskEligibilityPolicy: TaskEligibilityPolicy,
- taskOrderPolicy: TaskOrderPolicy,
- resourceFilterPolicy: ResourceFilterPolicy,
- resourceSelectionPolicy: ResourceSelectionPolicy
+ taskOrderPolicy: TaskOrderPolicy
) : WorkflowService {
+ /**
+ * The logger instance to use.
+ */
+ private val logger = KotlinLogging.logger {}
/**
* The incoming jobs ready to be processed by the scheduler.
@@ -97,25 +103,10 @@ public class StageWorkflowService(
internal val taskByServer = mutableMapOf<Server, TaskState>()
/**
- * The nodes that are controlled by the service.
- */
- internal lateinit var nodes: List<Node>
-
- /**
- * The available nodes.
- */
- internal val available: MutableSet<Node> = mutableSetOf()
-
- /**
- * The maximum number of incoming jobs.
- */
- private val throttleLimit: Int = 20000
-
- /**
* The load of the system.
*/
internal val load: Double
- get() = (available.size / nodes.size.toDouble())
+ get() = (activeTasks.size / provisioningService.hostCount.toDouble())
/**
* The root listener of this scheduler.
@@ -166,26 +157,23 @@ public class StageWorkflowService(
private val mode: WorkflowSchedulerMode.Logic
private val jobAdmissionPolicy: JobAdmissionPolicy.Logic
private val taskEligibilityPolicy: TaskEligibilityPolicy.Logic
- private val resourceFilterPolicy: ResourceFilterPolicy.Logic
- private val resourceSelectionPolicy: Comparator<Node>
- private val eventFlow = EventFlow<WorkflowEvent>()
init {
- coroutineScope.launch {
- nodes = provisioningService.nodes().toList()
- available.addAll(nodes)
- }
-
this.mode = mode(this)
this.jobAdmissionPolicy = jobAdmissionPolicy(this)
this.jobQueue = PriorityQueue(100, jobOrderPolicy(this).thenBy { it.job.uid })
this.taskEligibilityPolicy = taskEligibilityPolicy(this)
this.taskQueue = PriorityQueue(1000, taskOrderPolicy(this).thenBy { it.task.uid })
- this.resourceFilterPolicy = resourceFilterPolicy(this)
- this.resourceSelectionPolicy = resourceSelectionPolicy(this)
}
- override val events: Flow<WorkflowEvent> = eventFlow
+ override val events: Flow<WorkflowEvent> = tracer.openRecording().let {
+ it.enable<WorkflowEvent.JobSubmitted>()
+ it.enable<WorkflowEvent.JobStarted>()
+ it.enable<WorkflowEvent.JobFinished>()
+ it.enable<WorkflowEvent.TaskStarted>()
+ it.enable<WorkflowEvent.TaskFinished>()
+ it.consumeAsFlow().map { event -> event as WorkflowEvent }
+ }
override suspend fun submit(job: Job) {
// J1 Incoming Jobs
@@ -209,6 +197,7 @@ public class StageWorkflowService(
instances.values.toCollection(jobInstance.tasks)
incomingJobs += jobInstance
rootListener.jobSubmitted(jobInstance)
+ tracer.commit(WorkflowEvent.JobSubmitted(this, jobInstance.job))
requestCycle()
}
@@ -237,7 +226,7 @@ public class StageWorkflowService(
iterator.remove()
jobQueue.add(jobInstance)
activeJobs += jobInstance
- eventFlow.emit(WorkflowEvent.JobStarted(this, jobInstance.job, clock.millis()))
+ tracer.commit(WorkflowEvent.JobStarted(this, jobInstance.job))
rootListener.jobStarted(jobInstance)
}
@@ -279,26 +268,25 @@ public class StageWorkflowService(
// T3 Per task
while (taskQueue.isNotEmpty()) {
val instance = taskQueue.peek()
- val host: Node? = available.firstOrNull()
- if (host != null) {
- // T4 Submit task to machine
- available -= host
+ val cores = instance.task.metadata[WORKFLOW_TASK_CORES] as? Int ?: 1
+ val flavor = Flavor(cores, 1000) // TODO How to determine memory usage for workflow task
+ val image = instance.task.image
+ coroutineScope.launch {
+ val server = provisioningService.deploy(instance.task.name, image, flavor)
+
instance.state = TaskStatus.ACTIVE
- val newHost = provisioningService.deploy(host, instance.task.image)
- val server = newHost.server!!
- instance.host = newHost
+ instance.server = server
taskByServer[server] = instance
+
server.events
.onEach { event -> if (event is ServerEvent.StateChanged) stateChanged(event.server) }
.launchIn(coroutineScope)
-
- activeTasks += instance
- taskQueue.poll()
- rootListener.taskAssigned(instance)
- } else {
- break
}
+
+ activeTasks += instance
+ taskQueue.poll()
+ rootListener.taskAssigned(instance)
}
}
@@ -307,12 +295,11 @@ public class StageWorkflowService(
ServerState.ACTIVE -> {
val task = taskByServer.getValue(server)
task.startedAt = clock.millis()
- eventFlow.emit(
+ tracer.commit(
WorkflowEvent.TaskStarted(
this@StageWorkflowService,
task.job.job,
- task.task,
- clock.millis()
+ task.task
)
)
rootListener.taskStarted(task)
@@ -323,14 +310,12 @@ public class StageWorkflowService(
task.state = TaskStatus.FINISHED
task.finishedAt = clock.millis()
job.tasks.remove(task)
- available += task.host!!
activeTasks -= task
- eventFlow.emit(
+ tracer.commit(
WorkflowEvent.TaskFinished(
this@StageWorkflowService,
task.job.job,
- task.task,
- clock.millis()
+ task.task
)
)
rootListener.taskFinished(task)
@@ -355,9 +340,9 @@ public class StageWorkflowService(
}
}
- private suspend fun finishJob(job: JobState) {
+ private fun finishJob(job: JobState) {
activeJobs -= job
- eventFlow.emit(WorkflowEvent.JobFinished(this, job.job, clock.millis()))
+ tracer.commit(WorkflowEvent.JobFinished(this, job.job))
rootListener.jobFinished(job)
}
diff --git a/simulator/opendc-workflows/src/main/kotlin/org/opendc/workflows/service/TaskState.kt b/simulator/opendc-workflows/src/main/kotlin/org/opendc/workflows/service/TaskState.kt
index ed023c82..d1eb6704 100644
--- a/simulator/opendc-workflows/src/main/kotlin/org/opendc/workflows/service/TaskState.kt
+++ b/simulator/opendc-workflows/src/main/kotlin/org/opendc/workflows/service/TaskState.kt
@@ -22,7 +22,7 @@
package org.opendc.workflows.service
-import org.opendc.compute.core.metal.Node
+import org.opendc.compute.core.Server
import org.opendc.workflows.workload.Task
public class TaskState(public val job: JobState, public val task: Task) {
@@ -62,7 +62,7 @@ public class TaskState(public val job: JobState, public val task: Task) {
}
}
- public var host: Node? = null
+ public var server: Server? = null
/**
* Mark the specified [TaskView] as terminated.
diff --git a/simulator/opendc-workflows/src/main/kotlin/org/opendc/workflows/service/WorkflowEvent.kt b/simulator/opendc-workflows/src/main/kotlin/org/opendc/workflows/service/WorkflowEvent.kt
index dadccb50..bcf93562 100644
--- a/simulator/opendc-workflows/src/main/kotlin/org/opendc/workflows/service/WorkflowEvent.kt
+++ b/simulator/opendc-workflows/src/main/kotlin/org/opendc/workflows/service/WorkflowEvent.kt
@@ -22,25 +22,33 @@
package org.opendc.workflows.service
+import org.opendc.trace.core.Event
import org.opendc.workflows.workload.Job
import org.opendc.workflows.workload.Task
/**
* An event emitted by the [WorkflowService].
*/
-public sealed class WorkflowEvent {
+public sealed class WorkflowEvent : Event() {
/**
* The [WorkflowService] that emitted the event.
*/
public abstract val service: WorkflowService
/**
+ * This event is emitted when a job was submitted to the scheduler.
+ */
+ public data class JobSubmitted(
+ override val service: WorkflowService,
+ public val job: Job
+ ) : WorkflowEvent()
+
+ /**
* This event is emitted when a job has become active.
*/
public data class JobStarted(
override val service: WorkflowService,
- public val job: Job,
- public val time: Long
+ public val job: Job
) : WorkflowEvent()
/**
@@ -48,8 +56,7 @@ public sealed class WorkflowEvent {
*/
public data class JobFinished(
override val service: WorkflowService,
- public val job: Job,
- public val time: Long
+ public val job: Job
) : WorkflowEvent()
/**
@@ -58,8 +65,7 @@ public sealed class WorkflowEvent {
public data class TaskStarted(
override val service: WorkflowService,
public val job: Job,
- public val task: Task,
- public val time: Long
+ public val task: Task
) : WorkflowEvent()
/**
@@ -68,7 +74,6 @@ public sealed class WorkflowEvent {
public data class TaskFinished(
override val service: WorkflowService,
public val job: Job,
- public val task: Task,
- public val time: Long
+ public val task: Task
) : WorkflowEvent()
}
diff --git a/simulator/opendc-workflows/src/main/kotlin/org/opendc/workflows/service/WorkflowService.kt b/simulator/opendc-workflows/src/main/kotlin/org/opendc/workflows/service/WorkflowService.kt
index 319a8b85..b24f80da 100644
--- a/simulator/opendc-workflows/src/main/kotlin/org/opendc/workflows/service/WorkflowService.kt
+++ b/simulator/opendc-workflows/src/main/kotlin/org/opendc/workflows/service/WorkflowService.kt
@@ -34,7 +34,7 @@ import java.util.*
*/
public interface WorkflowService {
/**
- * Thie events emitted by the workflow scheduler.
+ * The events emitted by the workflow scheduler.
*/
public val events: Flow<WorkflowEvent>
diff --git a/simulator/opendc-workflows/src/main/kotlin/org/opendc/workflows/service/stage/resource/FunctionalResourceFilterPolicy.kt b/simulator/opendc-workflows/src/main/kotlin/org/opendc/workflows/service/stage/resource/FunctionalResourceFilterPolicy.kt
deleted file mode 100644
index ac79a9ce..00000000
--- a/simulator/opendc-workflows/src/main/kotlin/org/opendc/workflows/service/stage/resource/FunctionalResourceFilterPolicy.kt
+++ /dev/null
@@ -1,41 +0,0 @@
-/*
- * Copyright (c) 2020 AtLarge Research
- *
- * Permission is hereby granted, free of charge, to any person obtaining a copy
- * of this software and associated documentation files (the "Software"), to deal
- * in the Software without restriction, including without limitation the rights
- * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
- * copies of the Software, and to permit persons to whom the Software is
- * furnished to do so, subject to the following conditions:
- *
- * The above copyright notice and this permission notice shall be included in all
- * copies or substantial portions of the Software.
- *
- * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
- * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
- * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
- * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
- * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
- * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
- * SOFTWARE.
- */
-
-package org.opendc.workflows.service.stage.resource
-
-import org.opendc.compute.core.metal.Node
-import org.opendc.workflows.service.StageWorkflowService
-import org.opendc.workflows.service.TaskState
-
-/**
- * A [ResourceFilterPolicy] based on the amount of cores available on the machine and the cores required for
- * the task.
- */
-public object FunctionalResourceFilterPolicy : ResourceFilterPolicy {
- override fun invoke(scheduler: StageWorkflowService): ResourceFilterPolicy.Logic =
- object : ResourceFilterPolicy.Logic {
- override fun invoke(hosts: Sequence<Node>, task: TaskState): Sequence<Node> =
- hosts.filter { it in scheduler.available }
- }
-
- override fun toString(): String = "functional"
-}
diff --git a/simulator/opendc-workflows/src/main/kotlin/org/opendc/workflows/service/stage/resource/ResourceFilterPolicy.kt b/simulator/opendc-workflows/src/main/kotlin/org/opendc/workflows/service/stage/resource/ResourceFilterPolicy.kt
deleted file mode 100644
index 4923a34b..00000000
--- a/simulator/opendc-workflows/src/main/kotlin/org/opendc/workflows/service/stage/resource/ResourceFilterPolicy.kt
+++ /dev/null
@@ -1,45 +0,0 @@
-/*
- * Copyright (c) 2020 AtLarge Research
- *
- * Permission is hereby granted, free of charge, to any person obtaining a copy
- * of this software and associated documentation files (the "Software"), to deal
- * in the Software without restriction, including without limitation the rights
- * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
- * copies of the Software, and to permit persons to whom the Software is
- * furnished to do so, subject to the following conditions:
- *
- * The above copyright notice and this permission notice shall be included in all
- * copies or substantial portions of the Software.
- *
- * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
- * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
- * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
- * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
- * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
- * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
- * SOFTWARE.
- */
-
-package org.opendc.workflows.service.stage.resource
-
-import org.opendc.compute.core.metal.Node
-import org.opendc.workflows.service.TaskState
-import org.opendc.workflows.service.stage.StagePolicy
-
-/**
- * This interface represents stages **R2**, **R3** and **R4** stage of the Reference Architecture for Schedulers and
- * acts as a filter yielding a list of resources with sufficient resource-capacities, based on fixed or dynamic
- * requirements, and on predicted or monitored information about processing unit availability, memory occupancy, etc.
- */
-public interface ResourceFilterPolicy : StagePolicy<ResourceFilterPolicy.Logic> {
- public interface Logic {
- /**
- * Filter the list of machines based on dynamic information.
- *
- * @param hosts The hosts to filter.
- * @param task The task that is to be scheduled.
- * @return The machines on which the task can be scheduled.
- */
- public operator fun invoke(hosts: Sequence<Node>, task: TaskState): Sequence<Node>
- }
-}
diff --git a/simulator/opendc-workflows/src/main/kotlin/org/opendc/workflows/workload/Metadata.kt b/simulator/opendc-workflows/src/main/kotlin/org/opendc/workflows/workload/Metadata.kt
index d02e2b4e..4305aa57 100644
--- a/simulator/opendc-workflows/src/main/kotlin/org/opendc/workflows/workload/Metadata.kt
+++ b/simulator/opendc-workflows/src/main/kotlin/org/opendc/workflows/workload/Metadata.kt
@@ -28,3 +28,8 @@ package org.opendc.workflows.workload
* Meta-data key for the deadline of a task.
*/
public const val WORKFLOW_TASK_DEADLINE: String = "workflow:task:deadline"
+
+/**
+ * Meta-data key for the number of cores needed for a task.
+ */
+public const val WORKFLOW_TASK_CORES: String = "workflow:task:cores"
diff --git a/simulator/opendc-workflows/src/test/kotlin/org/opendc/workflows/service/StageWorkflowSchedulerIntegrationTest.kt b/simulator/opendc-workflows/src/test/kotlin/org/opendc/workflows/service/StageWorkflowSchedulerIntegrationTest.kt
index 90cf5b99..2bfcba35 100644
--- a/simulator/opendc-workflows/src/test/kotlin/org/opendc/workflows/service/StageWorkflowSchedulerIntegrationTest.kt
+++ b/simulator/opendc-workflows/src/test/kotlin/org/opendc/workflows/service/StageWorkflowSchedulerIntegrationTest.kt
@@ -35,14 +35,17 @@ import org.junit.jupiter.api.Assertions.assertEquals
import org.junit.jupiter.api.Assertions.assertNotEquals
import org.junit.jupiter.api.DisplayName
import org.junit.jupiter.api.Test
+import org.junit.jupiter.api.assertAll
import org.opendc.compute.core.metal.service.ProvisioningService
+import org.opendc.compute.simulator.SimVirtProvisioningService
+import org.opendc.compute.simulator.allocation.NumberOfActiveServersAllocationPolicy
import org.opendc.format.environment.sc18.Sc18EnvironmentReader
import org.opendc.format.trace.gwf.GwfTraceReader
+import org.opendc.simulator.compute.SimSpaceSharedHypervisorProvider
import org.opendc.simulator.utils.DelayControllerClockAdapter
+import org.opendc.trace.core.EventTracer
import org.opendc.workflows.service.stage.job.NullJobAdmissionPolicy
import org.opendc.workflows.service.stage.job.SubmissionTimeJobOrderPolicy
-import org.opendc.workflows.service.stage.resource.FirstFitResourceSelectionPolicy
-import org.opendc.workflows.service.stage.resource.FunctionalResourceFilterPolicy
import org.opendc.workflows.service.stage.task.NullTaskEligibilityPolicy
import org.opendc.workflows.service.stage.task.SubmissionTimeTaskOrderPolicy
import kotlin.math.max
@@ -57,7 +60,7 @@ internal class StageWorkflowSchedulerIntegrationTest {
* A large integration test where we check whether all tasks in some trace are executed correctly.
*/
@Test
- fun `should execute all tasks in trace`() {
+ fun testTrace() {
var jobsSubmitted = 0L
var jobsStarted = 0L
var jobsFinished = 0L
@@ -66,22 +69,32 @@ internal class StageWorkflowSchedulerIntegrationTest {
val testScope = TestCoroutineScope()
val clock = DelayControllerClockAdapter(testScope)
+ val tracer = EventTracer(clock)
val schedulerAsync = testScope.async {
val environment = Sc18EnvironmentReader(object {}.javaClass.getResourceAsStream("/environment.json"))
.use { it.construct(testScope, clock) }
+ val bareMetal = environment.platforms[0].zones[0].services[ProvisioningService]
+
+ // Wait for the bare metal nodes to be spawned
+ delay(10)
+
+ val provisioner = SimVirtProvisioningService(testScope, clock, bareMetal, NumberOfActiveServersAllocationPolicy(), tracer, SimSpaceSharedHypervisorProvider(), schedulingQuantum = 1000)
+
+ // Wait for the hypervisors to be spawned
+ delay(10)
+
StageWorkflowService(
testScope,
clock,
- environment.platforms[0].zones[0].services[ProvisioningService],
+ tracer,
+ provisioner,
mode = WorkflowSchedulerMode.Batch(100),
jobAdmissionPolicy = NullJobAdmissionPolicy,
jobOrderPolicy = SubmissionTimeJobOrderPolicy(),
taskEligibilityPolicy = NullTaskEligibilityPolicy,
taskOrderPolicy = SubmissionTimeTaskOrderPolicy(),
- resourceFilterPolicy = FunctionalResourceFilterPolicy,
- resourceSelectionPolicy = FirstFitResourceSelectionPolicy
)
}
@@ -106,16 +119,19 @@ internal class StageWorkflowSchedulerIntegrationTest {
while (reader.hasNext()) {
val (time, job) = reader.next()
jobsSubmitted++
- delay(max(0, time * 1000 - clock.millis()))
+ delay(max(0, time - clock.millis()))
scheduler.submit(job)
}
}
testScope.advanceUntilIdle()
- assertNotEquals(0, jobsSubmitted, "No jobs submitted")
- assertEquals(jobsSubmitted, jobsStarted, "Not all submitted jobs started")
- assertEquals(jobsSubmitted, jobsFinished, "Not all started jobs finished")
- assertEquals(tasksStarted, tasksFinished, "Not all started tasks finished")
+ assertAll(
+ { assertEquals(emptyList<Throwable>(), testScope.uncaughtExceptions) },
+ { assertNotEquals(0, jobsSubmitted, "No jobs submitted") },
+ { assertEquals(jobsSubmitted, jobsStarted, "Not all submitted jobs started") },
+ { assertEquals(jobsSubmitted, jobsFinished, "Not all started jobs finished") },
+ { assertEquals(tasksStarted, tasksFinished, "Not all started tasks finished") }
+ )
}
}
diff --git a/simulator/settings.gradle.kts b/simulator/settings.gradle.kts
index 935a18d0..470303f4 100644
--- a/simulator/settings.gradle.kts
+++ b/simulator/settings.gradle.kts
@@ -32,4 +32,6 @@ include(":opendc-runner-web")
include(":opendc-simulator:opendc-simulator-core")
include(":opendc-simulator:opendc-simulator-compute")
include(":opendc-simulator:opendc-simulator-failures")
+include(":opendc-trace:opendc-trace-core")
+include(":opendc-harness")
include(":opendc-utils")