From 13a3f376fec17d5dcb60b635414c64a6d6ea3b13 Mon Sep 17 00:00:00 2001 From: Dante Niewenhuis Date: Tue, 16 Sep 2025 18:41:42 +0200 Subject: updated workflow implementation for performance (#368) * Updated the workflow system for performance. Added workflow specific tests. --- .../opendc/experiments/base/ExperimentCliTest.kt | 60 +++ .../experiments/base/ExperimentRunnerTest.kt | 60 --- .../org/opendc/experiments/base/ExperimentTest.kt | 521 ------------------- .../opendc/experiments/base/ScenarioRunnerTest.kt | 521 +++++++++++++++++++ .../org/opendc/experiments/base/TestingUtils.kt | 6 +- .../org/opendc/experiments/base/WorkflowTest.kt | 561 +++++++++++++++++++++ 6 files changed, 1146 insertions(+), 583 deletions(-) create mode 100644 opendc-experiments/opendc-experiments-base/src/test/kotlin/org/opendc/experiments/base/ExperimentCliTest.kt delete mode 100644 opendc-experiments/opendc-experiments-base/src/test/kotlin/org/opendc/experiments/base/ExperimentRunnerTest.kt delete mode 100644 opendc-experiments/opendc-experiments-base/src/test/kotlin/org/opendc/experiments/base/ExperimentTest.kt create mode 100644 opendc-experiments/opendc-experiments-base/src/test/kotlin/org/opendc/experiments/base/ScenarioRunnerTest.kt create mode 100644 opendc-experiments/opendc-experiments-base/src/test/kotlin/org/opendc/experiments/base/WorkflowTest.kt (limited to 'opendc-experiments/opendc-experiments-base/src') diff --git a/opendc-experiments/opendc-experiments-base/src/test/kotlin/org/opendc/experiments/base/ExperimentCliTest.kt b/opendc-experiments/opendc-experiments-base/src/test/kotlin/org/opendc/experiments/base/ExperimentCliTest.kt new file mode 100644 index 00000000..748a020c --- /dev/null +++ b/opendc-experiments/opendc-experiments-base/src/test/kotlin/org/opendc/experiments/base/ExperimentCliTest.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.experiments.base + +import org.junit.jupiter.api.Test +import org.opendc.experiments.base.runner.ExperimentCommand +import java.io.File + +/** + * An integration test suite for the Experiment Runner. + */ +class ExperimentCliTest { + /** + * ExperimentCli test 1 + * This test runs the experiment defined in the experiment_1.json file. + * + * In this test, the bitbrains-small workload is executed with and without a carbon trace. + */ + @Test + fun testExperimentCli1() { + ExperimentCommand().main(arrayOf("--experiment-path", "src/test/resources/experiments/experiment_1.json")) + + val someDir = File("output") + someDir.deleteRecursively() + } + + /** + * ExperimentCli test 2 + * This test runs the experiment defined in the experiment_2.json file. + * + * In this test, parts of the Marconi 100 workload is executed . This trace contains GPU tasks. + */ + @Test + fun testExperimentCli2() { + ExperimentCommand().main(arrayOf("--experiment-path", "src/test/resources/experiments/experiment_2.json")) + + val someDir = File("output") + someDir.deleteRecursively() + } +} diff --git a/opendc-experiments/opendc-experiments-base/src/test/kotlin/org/opendc/experiments/base/ExperimentRunnerTest.kt b/opendc-experiments/opendc-experiments-base/src/test/kotlin/org/opendc/experiments/base/ExperimentRunnerTest.kt deleted file mode 100644 index cd16f174..00000000 --- a/opendc-experiments/opendc-experiments-base/src/test/kotlin/org/opendc/experiments/base/ExperimentRunnerTest.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.base - -import org.junit.jupiter.api.Test -import org.opendc.experiments.base.runner.ExperimentCommand -import java.io.File - -/** - * An integration test suite for the Experiment Runner. - */ -class ExperimentRunnerTest { - /** - * ExperimentRunner test 1 - * This test runs the experiment defined in the experiment_1.json file. - * - * In this test, the bitbrains-small workload is executed with and without a carbon trace. - */ - @Test - fun testExperimentRunner1() { - ExperimentCommand().main(arrayOf("--experiment-path", "src/test/resources/experiments/experiment_1.json")) - - val someDir = File("output") - someDir.deleteRecursively() - } - - /** - * ExperimentRunner test 2 - * This test runs the experiment defined in the experiment_2.json file. - * - * In this test, parts of the Marconi 100 workload is executed . This trace contains GPU tasks. - */ - @Test - fun testExperimentRunner2() { - ExperimentCommand().main(arrayOf("--experiment-path", "src/test/resources/experiments/experiment_2.json")) - - val someDir = File("output") - someDir.deleteRecursively() - } -} diff --git a/opendc-experiments/opendc-experiments-base/src/test/kotlin/org/opendc/experiments/base/ExperimentTest.kt b/opendc-experiments/opendc-experiments-base/src/test/kotlin/org/opendc/experiments/base/ExperimentTest.kt deleted file mode 100644 index 28096bb8..00000000 --- a/opendc-experiments/opendc-experiments-base/src/test/kotlin/org/opendc/experiments/base/ExperimentTest.kt +++ /dev/null @@ -1,521 +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.base - -import org.junit.jupiter.api.Assertions.assertEquals -import org.junit.jupiter.api.Test -import org.junit.jupiter.api.assertAll -import org.opendc.compute.simulator.scheduler.MemorizingScheduler -import org.opendc.compute.simulator.scheduler.filters.ComputeFilter -import org.opendc.compute.simulator.scheduler.filters.RamFilter -import org.opendc.compute.simulator.scheduler.filters.VCpuFilter -import org.opendc.compute.workload.Task -import org.opendc.simulator.compute.workload.trace.TraceFragment -import java.util.ArrayList - -/** - * An integration test suite for the Scenario experiments. - */ -class ExperimentTest { - /** - * Simulator test 1: Single Task - * In this test, a single task is scheduled that takes 10 minutes to run. - * - * There should be no problems running the task, so the total runtime should be 10 min. - * - * The task is using 50% of the available CPU capacity. - * This means that half of the time is active, and half is idle. - * When the task is failed, all time is idle. - */ - @Test - fun testSimulator1() { - val workload: ArrayList = - arrayListOf( - createTestTask( - id = 0, - fragments = - arrayListOf( - TraceFragment(10 * 60 * 1000, 1000.0), - ), - cpuCount = 1, - ), - ) - - val topology = createTopology("single_1_2000.json") - - val monitor = runTest(topology, workload) - - assertAll( - { assertEquals(10 * 60 * 1000, monitor.maxTimestamp) { "Total runtime incorrect" } }, - { assertEquals(((10 * 30000)).toLong(), monitor.hostCpuIdleTimes["H01"]?.sum()) { "Idle time incorrect" } }, - { assertEquals((10 * 30000).toLong(), monitor.hostCpuActiveTimes["H01"]?.sum()) { "Active time incorrect" } }, - { assertEquals(9000.0, monitor.hostEnergyUsages["H01"]?.get(0)) { "Incorrect host energy usage at timestamp 0" } }, - { assertEquals(600 * 150.0, monitor.hostEnergyUsages["H01"]?.sum()) { "Incorrect host energy usage" } }, - { assertEquals(600 * 150.0, monitor.energyUsages.sum()) { "Incorrect total energy usage" } }, - ) - } - - /** - * Simulator test 1: Two Tasks - * In this test, two tasks are scheduled. - * - * There should be no problems running the task, so the total runtime should be 15 min. - * - * The first task is using 50% of the available CPU capacity. - * The second task is using 100% of the available CPU capacity. - */ - @Test - fun testSimulator2() { - val workload: ArrayList = - arrayListOf( - createTestTask( - id = 0, - fragments = - arrayListOf( - TraceFragment(10 * 60 * 1000, 1000.0), - ), - cpuCount = 1, - ), - createTestTask( - id = 1, - fragments = - arrayListOf( - TraceFragment(5 * 60 * 1000, 2000.0), - ), - cpuCount = 1, - ), - ) - - val topology = createTopology("single_1_2000.json") - - val monitor = - runTest( - topology, - workload, - computeScheduler = - MemorizingScheduler( - filters = listOf(ComputeFilter(), VCpuFilter(1.0), RamFilter(1.0)), - ), - ) - - assertAll( - { assertEquals(15 * 60 * 1000, monitor.maxTimestamp) { "Total runtime incorrect" } }, - { assertEquals(((10 * 30000)).toLong(), monitor.hostCpuIdleTimes["H01"]?.sum()) { "Idle time incorrect" } }, - { assertEquals(((10 * 30000) + (5 * 60000)).toLong(), monitor.hostCpuActiveTimes["H01"]?.sum()) { "Active time incorrect" } }, - { assertEquals(9000.0, monitor.hostEnergyUsages["H01"]?.get(0)) { "Incorrect energy usage" } }, - { assertEquals((600 * 150.0) + (300 * 200.0), monitor.hostEnergyUsages["H01"]?.sum()) { "Incorrect energy usage" } }, - { assertEquals((600 * 150.0) + (300 * 200.0), monitor.energyUsages.sum()) { "Incorrect energy usage" } }, - ) - } - - /** - * Simulator test 3: Two Tasks, one scheduled later - * In this test, two tasks are scheduled. - * - * There should be no problems running the task, so the total runtime should be 15 min. - * - * The first task is using 50% of the available CPU capacity. - * The second task is using 100% of the available CPU capacity. - */ - @Test - fun testSimulator3() { - val workload: ArrayList = - arrayListOf( - createTestTask( - id = 0, - fragments = - arrayListOf( - TraceFragment(10 * 60 * 1000, 1000.0), - ), - cpuCount = 1, - ), - createTestTask( - id = 1, - fragments = - arrayListOf( - TraceFragment(10 * 60 * 1000, 1000.0), - ), - cpuCount = 1, - ), - ) - - val topology = createTopology("single_2_2000.json") - - val monitor = runTest(topology, workload) - - assertAll( - { assertEquals(10 * 60 * 1000, monitor.maxTimestamp) { "Total runtime incorrect" } }, - { assertEquals(((10 * 30000)).toLong(), monitor.hostCpuIdleTimes["H01"]?.sum()) { "Idle time incorrect" } }, - { assertEquals(((10 * 30000)).toLong(), monitor.hostCpuActiveTimes["H01"]?.sum()) { "Active time incorrect" } }, - { assertEquals(9000.0, monitor.hostEnergyUsages["H01"]?.get(0)) { "Incorrect energy usage" } }, - { assertEquals((600 * 150.0), monitor.hostEnergyUsages["H01"]?.sum()) { "Incorrect energy usage" } }, - { assertEquals((600 * 150.0), monitor.energyUsages.sum()) { "Incorrect energy usage" } }, - ) - } - - /** - * Simulator test 4: Two Tasks, one scheduled later - * In this test, two tasks are scheduled. - * - * There should be no problems running the task, so the total runtime should be 15 min. - * - * The first task is using 50% of the available CPU capacity. - * The second task is using 100% of the available CPU capacity. - */ - @Test - fun testSimulator4() { - val workload: ArrayList = - arrayListOf( - createTestTask( - id = 0, - fragments = - arrayListOf( - TraceFragment(10 * 60 * 1000, 1000.0), - ), - cpuCount = 1, - ), - createTestTask( - id = 1, - fragments = - arrayListOf( - TraceFragment(5 * 60 * 1000, 2000.0), - ), - cpuCount = 1, - submissionTime = "1970-01-01T00:20", - ), - ) - - val topology = createTopology("single_1_2000.json") - - val monitor = runTest(topology, workload) - - assertAll( - { assertEquals(25 * 60 * 1000, monitor.maxTimestamp) { "Total runtime incorrect" } }, - { assertEquals(((10 * 30000) + (10 * 60000)).toLong(), monitor.hostCpuIdleTimes["H01"]?.sum()) { "Idle time incorrect" } }, - { assertEquals(((10 * 30000) + (5 * 60000)).toLong(), monitor.hostCpuActiveTimes["H01"]?.sum()) { "Active time incorrect" } }, - { assertEquals(9000.0, monitor.hostEnergyUsages["H01"]?.get(0)) { "Incorrect energy usage" } }, - { - assertEquals( - (600 * 150.0) + (600 * 100.0) + (300 * 200.0), - monitor.hostEnergyUsages["H01"]?.sum(), - ) { "Incorrect energy usage" } - }, - ) - } - - /** - * Simulator test 5: One Task purely running on GPU - * - * In this test, a single task is scheduled that takes 10 minutes to run. It solely uses the GPU. - */ - @Test - fun testSimulator5() { - val workload: ArrayList = - arrayListOf( - createTestTask( - id = 0, - fragments = - arrayListOf( - TraceFragment(10 * 60 * 1000, 0.0, 1000.0), - ), - cpuCount = 0, - gpuCount = 1, - ), - ) - - val topology = createTopology("Gpus/single_gpu_no_vendor_no_memory.json") - - val monitor = runTest(topology, workload) - - assertAll( - { assertEquals(10 * 60 * 1000, monitor.maxTimestamp) { "Total runtime incorrect" } }, - { assertEquals(((10 * 60 * 1000)).toLong(), monitor.hostCpuIdleTimes["H01"]?.sum()) { "CPU Idle time incorrect" } }, - { assertEquals(0L, monitor.hostCpuActiveTimes["H01"]?.sum()) { "CPU Active time incorrect" } }, - { - assertEquals( - ((10 * 30000)).toLong(), - monitor.hostGpuIdleTimes["H01"]?.fold(0, { acc, iterator -> acc + iterator[0] }), - ) { "GPU Idle time incorrect" } - }, - { - assertEquals( - ((10 * 30000)).toLong(), - monitor.hostGpuActiveTimes["H01"]?.fold(0, { acc, iterator -> acc + iterator[0] }), - ) { "GPU Active time incorrect" } - }, - // double, as CPU and GPU both use power - // higher power usage, as default GPU power model is used range [200, 400] - { assertEquals(2 * 12000.0, monitor.hostEnergyUsages["H01"]?.get(0)) { "Incorrect host energy usage at timestamp 0" } }, - { assertEquals((600 * 100.0) + (600 * 300.0), monitor.hostEnergyUsages["H01"]?.sum()) { "Incorrect host energy usage" } }, - { assertEquals((600 * 100.0) + (600 * 300.0), monitor.energyUsages.sum()) { "Incorrect total energy usage" } }, - ) - } - - /** - * Simulator test 6: One Task running on CPU & GPU - * - * In this test, a single task is scheduled that takes 10 minutes to run. CPU & GPU are used and have the same runtime. - */ - @Test - fun testSimulator6() { - val workload: ArrayList = - arrayListOf( - createTestTask( - id = 0, - fragments = - arrayListOf( - TraceFragment(10 * 60 * 1000, 1000.0, 1000.0), - ), - cpuCount = 1, - gpuCount = 1, - ), - ) - - val topology = createTopology("Gpus/single_gpu_no_vendor_no_memory.json") - - val monitor = runTest(topology, workload) - - assertAll( - { assertEquals(10 * 60 * 1000, monitor.maxTimestamp) { "Total runtime incorrect" } }, - { assertEquals(((10 * 30000)).toLong(), monitor.hostCpuIdleTimes["H01"]?.sum()) { "CPU Idle time incorrect" } }, - { assertEquals(((10 * 30000)).toLong(), monitor.hostCpuActiveTimes["H01"]?.sum()) { "CPU Active time incorrect" } }, - { - assertEquals( - ((10 * 30000)).toLong(), - monitor.hostGpuIdleTimes["H01"]?.fold(0, { acc, iterator -> acc + iterator[0] }), - ) { "GPU Idle time incorrect" } - }, - { - assertEquals( - ((10 * 30000)).toLong(), - monitor.hostGpuActiveTimes["H01"]?.fold(0, { acc, iterator -> acc + iterator[0] }), - ) { "GPU Active time incorrect" } - }, - // double, as CPU and GPU both use power - { assertEquals(27000.0, monitor.hostEnergyUsages["H01"]?.get(0)) { "Incorrect host energy usage at timestamp 0" } }, - { assertEquals((600 * 150.0) + (600 * 300.0), monitor.hostEnergyUsages["H01"]?.sum()) { "Incorrect host energy usage" } }, - { assertEquals((600 * 150.0) + (600 * 300.0), monitor.energyUsages.sum()) { "Incorrect total energy usage" } }, - ) - } - - /** - * Simulator test 7: One Task running on CPU & GPU - * - * In this test, a single task is scheduled that takes 10 minutes to run. CPU & GPU are used. CPU will finish way ahead of the GPU. - */ - @Test - fun testSimulator7() { - val workload: ArrayList = - arrayListOf( - createTestTask( - id = 0, - fragments = - arrayListOf( - TraceFragment(10 * 60 * 1000, 1000.0, 2000.0), - ), - cpuCount = 1, - gpuCount = 1, - ), - ) - - val topology = createTopology("Gpus/single_gpu_no_vendor_no_memory.json") - - val monitor = runTest(topology, workload) - assertAll( - { assertEquals(10 * 60 * 1000, monitor.maxTimestamp) { "Total runtime incorrect" } }, - { assertEquals(((10 * 30000)).toLong(), monitor.hostCpuIdleTimes["H01"]?.sum()) { "CPU Idle time incorrect" } }, - { assertEquals(((10 * 30000)).toLong(), monitor.hostCpuActiveTimes["H01"]?.sum()) { "CPU Active time incorrect" } }, - { - assertEquals( - 0L, - monitor.hostGpuIdleTimes["H01"]?.fold(0, { acc, iterator -> acc + iterator[0] }), - ) { "GPU Idle time incorrect" } - }, - { - assertEquals( - ((10 * 60000)).toLong(), - monitor.hostGpuActiveTimes["H01"]?.fold(0, { acc, iterator -> acc + iterator[0] }), - ) { "GPU Active time incorrect" } - }, - // double, as CPU and GPU both use power - { assertEquals(33000.0, monitor.hostEnergyUsages["H01"]?.get(0)) { "Incorrect host energy usage at timestamp 0" } }, - { assertEquals((600 * 150.0) + (600 * 400.0), monitor.hostEnergyUsages["H01"]?.sum()) { "Incorrect host energy usage" } }, - { assertEquals((600 * 150.0) + (600 * 400.0), monitor.energyUsages.sum()) { "Incorrect total energy usage" } }, - ) - } - - /** - * Simulator test 8: One Task running on CPU & GPU - * - * In this test, a single task is scheduled that takes 10 minutes to run. CPU & GPU are used. GPU will finish way ahead of the CPU. - */ - @Test - fun testSimulator8() { - val workload: ArrayList = - arrayListOf( - createTestTask( - id = 0, - fragments = - arrayListOf( - TraceFragment(10 * 60 * 1000, 2000.0, 1000.0), - ), - cpuCount = 1, - gpuCount = 1, - ), - ) - val topology = createTopology("Gpus/single_gpu_no_vendor_no_memory.json") - val monitor = runTest(topology, workload) - - assertAll( - { assertEquals(10 * 60 * 1000, monitor.maxTimestamp) { "Total runtime incorrect" } }, - { assertEquals(0L, monitor.hostCpuIdleTimes["H01"]?.sum()) { "CPU Idle time incorrect" } }, - { assertEquals(((10 * 60000)).toLong(), monitor.hostCpuActiveTimes["H01"]?.sum()) { "CPU Active time incorrect" } }, - { - assertEquals( - ((10 * 30000)).toLong(), - monitor.hostGpuIdleTimes["H01"]?.fold(0, { acc, iterator -> acc + iterator[0] }), - ) { "GPU Idle time incorrect" } - }, - { - assertEquals( - ((10 * 30000)).toLong(), - monitor.hostGpuActiveTimes["H01"]?.fold(0, { acc, iterator -> acc + iterator[0] }), - ) { "GPU Active time incorrect" } - }, - // double, as CPU and GPU both use power - { assertEquals(30000.0, monitor.hostEnergyUsages["H01"]?.get(0)) { "Incorrect host energy usage at timestamp 0" } }, - { assertEquals((600 * 200.0) + (600 * 300.0), monitor.hostEnergyUsages["H01"]?.sum()) { "Incorrect host energy usage" } }, - { assertEquals((600 * 200.0) + (600 * 300.0), monitor.energyUsages.sum()) { "Incorrect total energy usage" } }, - ) - } - - /** - * Simulator test 9: Two tasks running on CPU & GPU - * - * In this test, two tasks are scheduled at the same time that takes 10 minutes to run. CPU & GPU are used. Both resources will finish at the same time. - */ - @Test - fun testSimulator9() { - val workload: ArrayList = - arrayListOf( - createTestTask( - id = 0, - fragments = - arrayListOf( - TraceFragment(10 * 60 * 1000, 1000.0, 1000.0), - ), - cpuCount = 1, - gpuCount = 1, - ), - createTestTask( - id = 1, - fragments = - arrayListOf( - TraceFragment(10 * 60 * 1000, 1000.0, 1000.0), - ), - cpuCount = 1, - gpuCount = 1, - ), - ) - - val topology = createTopology("Gpus/single_gpu_no_vendor_no_memory.json") - val monitor = runTest(topology, workload) - - assertAll( - { assertEquals(2 * (10 * 60 * 1000), monitor.maxTimestamp) { "Total runtime incorrect" } }, - { assertEquals(((10 * 60000)).toLong(), monitor.hostCpuIdleTimes["H01"]?.sum()) { "CPU Idle time incorrect" } }, - { assertEquals(((10 * 60000)).toLong(), monitor.hostCpuActiveTimes["H01"]?.sum()) { "CPU Active time incorrect" } }, - { - assertEquals( - ((10 * 60000)).toLong(), - monitor.hostGpuIdleTimes["H01"]?.fold(0, { acc, iterator -> acc + iterator[0] }), - ) { "GPU Idle time incorrect" } - }, - { - assertEquals( - ((10 * 60000)).toLong(), - monitor.hostGpuActiveTimes["H01"]?.fold(0, { acc, iterator -> acc + iterator[0] }), - ) { "GPU Active time incorrect" } - }, - // double, as CPU and GPU both use power - { assertEquals(27000.0, monitor.hostEnergyUsages["H01"]?.get(0)) { "Incorrect host energy usage at timestamp 0" } }, - { assertEquals(2 * ((600 * 150.0) + (600 * 300.0)), monitor.hostEnergyUsages["H01"]?.sum()) { "Incorrect host energy usage" } }, - { assertEquals(2 * ((600 * 150.0) + (600 * 300.0)), monitor.energyUsages.sum()) { "Incorrect total energy usage" } }, - ) - } - - /** - * Simulator test 10: Two tasks running on CPU & GPU - * - * In this test, two tasks are scheduled at the same time that takes 10 minutes to run. One task purely uses CPU, one purely GPU. - */ - @Test - fun testSimulator10() { - val workload: ArrayList = - arrayListOf( - createTestTask( - id = 0, - fragments = - arrayListOf( - TraceFragment(10 * 60 * 1000, 1000.0, 0.0), - ), - cpuCount = 1, - gpuCount = 0, - ), - createTestTask( - id = 1, - fragments = - arrayListOf( - TraceFragment(10 * 60 * 1000, 0.0, 1000.0), - ), - cpuCount = 0, - gpuCount = 1, - ), - ) - - val topology = createTopology("Gpus/single_gpu_no_vendor_no_memory.json") - val monitor = runTest(topology, workload) - - assertAll( - { assertEquals(10 * 60 * 1000, monitor.maxTimestamp) { "Total runtime incorrect" } }, - { assertEquals(((10 * 30000)).toLong(), monitor.hostCpuIdleTimes["H01"]?.sum()) { "CPU Idle time incorrect" } }, - { assertEquals(((10 * 30000)).toLong(), monitor.hostCpuActiveTimes["H01"]?.sum()) { "CPU Active time incorrect" } }, - { - assertEquals( - ((10 * 30000)).toLong(), - monitor.hostGpuIdleTimes["H01"]?.fold(0, { acc, iterator -> acc + iterator[0] }), - ) { "GPU Idle time incorrect" } - }, - { - assertEquals( - ((10 * 30000)).toLong(), - monitor.hostGpuActiveTimes["H01"]?.fold(0, { acc, iterator -> acc + iterator[0] }), - ) { "GPU Active time incorrect" } - }, - // double, as CPU and GPU both use power - { assertEquals(27000.0, monitor.hostEnergyUsages["H01"]?.get(0)) { "Incorrect host energy usage at timestamp 0" } }, - { assertEquals((600 * 150.0) + (600 * 300.0), monitor.hostEnergyUsages["H01"]?.sum()) { "Incorrect host energy usage" } }, - { assertEquals((600 * 150.0) + (600 * 300.0), monitor.energyUsages.sum()) { "Incorrect total energy usage" } }, - ) - } -} diff --git a/opendc-experiments/opendc-experiments-base/src/test/kotlin/org/opendc/experiments/base/ScenarioRunnerTest.kt b/opendc-experiments/opendc-experiments-base/src/test/kotlin/org/opendc/experiments/base/ScenarioRunnerTest.kt new file mode 100644 index 00000000..f43dfbb0 --- /dev/null +++ b/opendc-experiments/opendc-experiments-base/src/test/kotlin/org/opendc/experiments/base/ScenarioRunnerTest.kt @@ -0,0 +1,521 @@ +/* + * 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.base + +import org.junit.jupiter.api.Assertions.assertEquals +import org.junit.jupiter.api.Test +import org.junit.jupiter.api.assertAll +import org.opendc.compute.simulator.scheduler.MemorizingScheduler +import org.opendc.compute.simulator.scheduler.filters.ComputeFilter +import org.opendc.compute.simulator.scheduler.filters.RamFilter +import org.opendc.compute.simulator.scheduler.filters.VCpuFilter +import org.opendc.compute.workload.Task +import org.opendc.simulator.compute.workload.trace.TraceFragment +import java.util.ArrayList + +/** + * An integration test suite for the Scenario experiments. + */ +class ScenarioRunnerTest { + /** + * Scenario test 1: Single Task + * In this test, a single task is scheduled that takes 10 minutes to run. + * + * There should be no problems running the task, so the total runtime should be 10 min. + * + * The task is using 50% of the available CPU capacity. + * This means that half of the time is active, and half is idle. + * When the task is failed, all time is idle. + */ + @Test + fun testScenario1() { + val workload: ArrayList = + arrayListOf( + createTestTask( + id = 0, + fragments = + arrayListOf( + TraceFragment(10 * 60 * 1000, 1000.0), + ), + cpuCount = 1, + ), + ) + + val topology = createTopology("single_1_2000.json") + + val monitor = runTest(topology, workload) + + assertAll( + { assertEquals(10 * 60 * 1000, monitor.maxTimestamp) { "Total runtime incorrect" } }, + { assertEquals(((10 * 30000)).toLong(), monitor.hostCpuIdleTimes["H01"]?.sum()) { "Idle time incorrect" } }, + { assertEquals((10 * 30000).toLong(), monitor.hostCpuActiveTimes["H01"]?.sum()) { "Active time incorrect" } }, + { assertEquals(9000.0, monitor.hostEnergyUsages["H01"]?.get(0)) { "Incorrect host energy usage at timestamp 0" } }, + { assertEquals(600 * 150.0, monitor.hostEnergyUsages["H01"]?.sum()) { "Incorrect host energy usage" } }, + { assertEquals(600 * 150.0, monitor.energyUsages.sum()) { "Incorrect total energy usage" } }, + ) + } + + /** + * Scenario test 1: Two Tasks + * In this test, two tasks are scheduled. + * + * There should be no problems running the task, so the total runtime should be 15 min. + * + * The first task is using 50% of the available CPU capacity. + * The second task is using 100% of the available CPU capacity. + */ + @Test + fun testScenario2() { + val workload: ArrayList = + arrayListOf( + createTestTask( + id = 0, + fragments = + arrayListOf( + TraceFragment(10 * 60 * 1000, 1000.0), + ), + cpuCount = 1, + ), + createTestTask( + id = 1, + fragments = + arrayListOf( + TraceFragment(5 * 60 * 1000, 2000.0), + ), + cpuCount = 1, + ), + ) + + val topology = createTopology("single_1_2000.json") + + val monitor = + runTest( + topology, + workload, + computeScheduler = + MemorizingScheduler( + filters = listOf(ComputeFilter(), VCpuFilter(1.0), RamFilter(1.0)), + ), + ) + + assertAll( + { assertEquals(15 * 60 * 1000, monitor.maxTimestamp) { "Total runtime incorrect" } }, + { assertEquals(((10 * 30000)).toLong(), monitor.hostCpuIdleTimes["H01"]?.sum()) { "Idle time incorrect" } }, + { assertEquals(((10 * 30000) + (5 * 60000)).toLong(), monitor.hostCpuActiveTimes["H01"]?.sum()) { "Active time incorrect" } }, + { assertEquals(9000.0, monitor.hostEnergyUsages["H01"]?.get(0)) { "Incorrect energy usage" } }, + { assertEquals((600 * 150.0) + (300 * 200.0), monitor.hostEnergyUsages["H01"]?.sum()) { "Incorrect energy usage" } }, + { assertEquals((600 * 150.0) + (300 * 200.0), monitor.energyUsages.sum()) { "Incorrect energy usage" } }, + ) + } + + /** + * Scenario test 3: Two Tasks, one scheduled later + * In this test, two tasks are scheduled. + * + * There should be no problems running the task, so the total runtime should be 15 min. + * + * The first task is using 50% of the available CPU capacity. + * The second task is using 100% of the available CPU capacity. + */ + @Test + fun testScenario3() { + val workload: ArrayList = + arrayListOf( + createTestTask( + id = 0, + fragments = + arrayListOf( + TraceFragment(10 * 60 * 1000, 1000.0), + ), + cpuCount = 1, + ), + createTestTask( + id = 1, + fragments = + arrayListOf( + TraceFragment(10 * 60 * 1000, 1000.0), + ), + cpuCount = 1, + ), + ) + + val topology = createTopology("single_2_2000.json") + + val monitor = runTest(topology, workload) + + assertAll( + { assertEquals(10 * 60 * 1000, monitor.maxTimestamp) { "Total runtime incorrect" } }, + { assertEquals(((10 * 30000)).toLong(), monitor.hostCpuIdleTimes["H01"]?.sum()) { "Idle time incorrect" } }, + { assertEquals(((10 * 30000)).toLong(), monitor.hostCpuActiveTimes["H01"]?.sum()) { "Active time incorrect" } }, + { assertEquals(9000.0, monitor.hostEnergyUsages["H01"]?.get(0)) { "Incorrect energy usage" } }, + { assertEquals((600 * 150.0), monitor.hostEnergyUsages["H01"]?.sum()) { "Incorrect energy usage" } }, + { assertEquals((600 * 150.0), monitor.energyUsages.sum()) { "Incorrect energy usage" } }, + ) + } + + /** + * Scenario test 4: Two Tasks, one scheduled later + * In this test, two tasks are scheduled. + * + * There should be no problems running the task, so the total runtime should be 15 min. + * + * The first task is using 50% of the available CPU capacity. + * The second task is using 100% of the available CPU capacity. + */ + @Test + fun testScenario4() { + val workload: ArrayList = + arrayListOf( + createTestTask( + id = 0, + fragments = + arrayListOf( + TraceFragment(10 * 60 * 1000, 1000.0), + ), + cpuCount = 1, + ), + createTestTask( + id = 1, + fragments = + arrayListOf( + TraceFragment(5 * 60 * 1000, 2000.0), + ), + cpuCount = 1, + submissionTime = "1970-01-01T00:20", + ), + ) + + val topology = createTopology("single_1_2000.json") + + val monitor = runTest(topology, workload) + + assertAll( + { assertEquals(25 * 60 * 1000, monitor.maxTimestamp) { "Total runtime incorrect" } }, + { assertEquals(((10 * 30000) + (10 * 60000)).toLong(), monitor.hostCpuIdleTimes["H01"]?.sum()) { "Idle time incorrect" } }, + { assertEquals(((10 * 30000) + (5 * 60000)).toLong(), monitor.hostCpuActiveTimes["H01"]?.sum()) { "Active time incorrect" } }, + { assertEquals(9000.0, monitor.hostEnergyUsages["H01"]?.get(0)) { "Incorrect energy usage" } }, + { + assertEquals( + (600 * 150.0) + (600 * 100.0) + (300 * 200.0), + monitor.hostEnergyUsages["H01"]?.sum(), + ) { "Incorrect energy usage" } + }, + ) + } + + /** + * Scenario test 5: One Task purely running on GPU + * + * In this test, a single task is scheduled that takes 10 minutes to run. It solely uses the GPU. + */ + @Test + fun testScenario5() { + val workload: ArrayList = + arrayListOf( + createTestTask( + id = 0, + fragments = + arrayListOf( + TraceFragment(10 * 60 * 1000, 0.0, 1000.0), + ), + cpuCount = 0, + gpuCount = 1, + ), + ) + + val topology = createTopology("Gpus/single_gpu_no_vendor_no_memory.json") + + val monitor = runTest(topology, workload) + + assertAll( + { assertEquals(10 * 60 * 1000, monitor.maxTimestamp) { "Total runtime incorrect" } }, + { assertEquals(((10 * 60 * 1000)).toLong(), monitor.hostCpuIdleTimes["H01"]?.sum()) { "CPU Idle time incorrect" } }, + { assertEquals(0L, monitor.hostCpuActiveTimes["H01"]?.sum()) { "CPU Active time incorrect" } }, + { + assertEquals( + ((10 * 30000)).toLong(), + monitor.hostGpuIdleTimes["H01"]?.fold(0, { acc, iterator -> acc + iterator[0] }), + ) { "GPU Idle time incorrect" } + }, + { + assertEquals( + ((10 * 30000)).toLong(), + monitor.hostGpuActiveTimes["H01"]?.fold(0, { acc, iterator -> acc + iterator[0] }), + ) { "GPU Active time incorrect" } + }, + // double, as CPU and GPU both use power + // higher power usage, as default GPU power model is used range [200, 400] + { assertEquals(2 * 12000.0, monitor.hostEnergyUsages["H01"]?.get(0)) { "Incorrect host energy usage at timestamp 0" } }, + { assertEquals((600 * 100.0) + (600 * 300.0), monitor.hostEnergyUsages["H01"]?.sum()) { "Incorrect host energy usage" } }, + { assertEquals((600 * 100.0) + (600 * 300.0), monitor.energyUsages.sum()) { "Incorrect total energy usage" } }, + ) + } + + /** + * Scenario test 6: One Task running on CPU & GPU + * + * In this test, a single task is scheduled that takes 10 minutes to run. CPU & GPU are used and have the same runtime. + */ + @Test + fun testScenario6() { + val workload: ArrayList = + arrayListOf( + createTestTask( + id = 0, + fragments = + arrayListOf( + TraceFragment(10 * 60 * 1000, 1000.0, 1000.0), + ), + cpuCount = 1, + gpuCount = 1, + ), + ) + + val topology = createTopology("Gpus/single_gpu_no_vendor_no_memory.json") + + val monitor = runTest(topology, workload) + + assertAll( + { assertEquals(10 * 60 * 1000, monitor.maxTimestamp) { "Total runtime incorrect" } }, + { assertEquals(((10 * 30000)).toLong(), monitor.hostCpuIdleTimes["H01"]?.sum()) { "CPU Idle time incorrect" } }, + { assertEquals(((10 * 30000)).toLong(), monitor.hostCpuActiveTimes["H01"]?.sum()) { "CPU Active time incorrect" } }, + { + assertEquals( + ((10 * 30000)).toLong(), + monitor.hostGpuIdleTimes["H01"]?.fold(0, { acc, iterator -> acc + iterator[0] }), + ) { "GPU Idle time incorrect" } + }, + { + assertEquals( + ((10 * 30000)).toLong(), + monitor.hostGpuActiveTimes["H01"]?.fold(0, { acc, iterator -> acc + iterator[0] }), + ) { "GPU Active time incorrect" } + }, + // double, as CPU and GPU both use power + { assertEquals(27000.0, monitor.hostEnergyUsages["H01"]?.get(0)) { "Incorrect host energy usage at timestamp 0" } }, + { assertEquals((600 * 150.0) + (600 * 300.0), monitor.hostEnergyUsages["H01"]?.sum()) { "Incorrect host energy usage" } }, + { assertEquals((600 * 150.0) + (600 * 300.0), monitor.energyUsages.sum()) { "Incorrect total energy usage" } }, + ) + } + + /** + * Scenario test 7: One Task running on CPU & GPU + * + * In this test, a single task is scheduled that takes 10 minutes to run. CPU & GPU are used. CPU will finish way ahead of the GPU. + */ + @Test + fun testScenario7() { + val workload: ArrayList = + arrayListOf( + createTestTask( + id = 0, + fragments = + arrayListOf( + TraceFragment(10 * 60 * 1000, 1000.0, 2000.0), + ), + cpuCount = 1, + gpuCount = 1, + ), + ) + + val topology = createTopology("Gpus/single_gpu_no_vendor_no_memory.json") + + val monitor = runTest(topology, workload) + assertAll( + { assertEquals(10 * 60 * 1000, monitor.maxTimestamp) { "Total runtime incorrect" } }, + { assertEquals(((10 * 30000)).toLong(), monitor.hostCpuIdleTimes["H01"]?.sum()) { "CPU Idle time incorrect" } }, + { assertEquals(((10 * 30000)).toLong(), monitor.hostCpuActiveTimes["H01"]?.sum()) { "CPU Active time incorrect" } }, + { + assertEquals( + 0L, + monitor.hostGpuIdleTimes["H01"]?.fold(0, { acc, iterator -> acc + iterator[0] }), + ) { "GPU Idle time incorrect" } + }, + { + assertEquals( + ((10 * 60000)).toLong(), + monitor.hostGpuActiveTimes["H01"]?.fold(0, { acc, iterator -> acc + iterator[0] }), + ) { "GPU Active time incorrect" } + }, + // double, as CPU and GPU both use power + { assertEquals(33000.0, monitor.hostEnergyUsages["H01"]?.get(0)) { "Incorrect host energy usage at timestamp 0" } }, + { assertEquals((600 * 150.0) + (600 * 400.0), monitor.hostEnergyUsages["H01"]?.sum()) { "Incorrect host energy usage" } }, + { assertEquals((600 * 150.0) + (600 * 400.0), monitor.energyUsages.sum()) { "Incorrect total energy usage" } }, + ) + } + + /** + * Scenario test 8: One Task running on CPU & GPU + * + * In this test, a single task is scheduled that takes 10 minutes to run. CPU & GPU are used. GPU will finish way ahead of the CPU. + */ + @Test + fun testScenario8() { + val workload: ArrayList = + arrayListOf( + createTestTask( + id = 0, + fragments = + arrayListOf( + TraceFragment(10 * 60 * 1000, 2000.0, 1000.0), + ), + cpuCount = 1, + gpuCount = 1, + ), + ) + val topology = createTopology("Gpus/single_gpu_no_vendor_no_memory.json") + val monitor = runTest(topology, workload) + + assertAll( + { assertEquals(10 * 60 * 1000, monitor.maxTimestamp) { "Total runtime incorrect" } }, + { assertEquals(0L, monitor.hostCpuIdleTimes["H01"]?.sum()) { "CPU Idle time incorrect" } }, + { assertEquals(((10 * 60000)).toLong(), monitor.hostCpuActiveTimes["H01"]?.sum()) { "CPU Active time incorrect" } }, + { + assertEquals( + ((10 * 30000)).toLong(), + monitor.hostGpuIdleTimes["H01"]?.fold(0, { acc, iterator -> acc + iterator[0] }), + ) { "GPU Idle time incorrect" } + }, + { + assertEquals( + ((10 * 30000)).toLong(), + monitor.hostGpuActiveTimes["H01"]?.fold(0, { acc, iterator -> acc + iterator[0] }), + ) { "GPU Active time incorrect" } + }, + // double, as CPU and GPU both use power + { assertEquals(30000.0, monitor.hostEnergyUsages["H01"]?.get(0)) { "Incorrect host energy usage at timestamp 0" } }, + { assertEquals((600 * 200.0) + (600 * 300.0), monitor.hostEnergyUsages["H01"]?.sum()) { "Incorrect host energy usage" } }, + { assertEquals((600 * 200.0) + (600 * 300.0), monitor.energyUsages.sum()) { "Incorrect total energy usage" } }, + ) + } + + /** + * Scenario test 9: Two tasks running on CPU & GPU + * + * In this test, two tasks are scheduled at the same time that takes 10 minutes to run. CPU & GPU are used. Both resources will finish at the same time. + */ + @Test + fun testScenario9() { + val workload: ArrayList = + arrayListOf( + createTestTask( + id = 0, + fragments = + arrayListOf( + TraceFragment(10 * 60 * 1000, 1000.0, 1000.0), + ), + cpuCount = 1, + gpuCount = 1, + ), + createTestTask( + id = 1, + fragments = + arrayListOf( + TraceFragment(10 * 60 * 1000, 1000.0, 1000.0), + ), + cpuCount = 1, + gpuCount = 1, + ), + ) + + val topology = createTopology("Gpus/single_gpu_no_vendor_no_memory.json") + val monitor = runTest(topology, workload) + + assertAll( + { assertEquals(2 * (10 * 60 * 1000), monitor.maxTimestamp) { "Total runtime incorrect" } }, + { assertEquals(((10 * 60000)).toLong(), monitor.hostCpuIdleTimes["H01"]?.sum()) { "CPU Idle time incorrect" } }, + { assertEquals(((10 * 60000)).toLong(), monitor.hostCpuActiveTimes["H01"]?.sum()) { "CPU Active time incorrect" } }, + { + assertEquals( + ((10 * 60000)).toLong(), + monitor.hostGpuIdleTimes["H01"]?.fold(0, { acc, iterator -> acc + iterator[0] }), + ) { "GPU Idle time incorrect" } + }, + { + assertEquals( + ((10 * 60000)).toLong(), + monitor.hostGpuActiveTimes["H01"]?.fold(0, { acc, iterator -> acc + iterator[0] }), + ) { "GPU Active time incorrect" } + }, + // double, as CPU and GPU both use power + { assertEquals(27000.0, monitor.hostEnergyUsages["H01"]?.get(0)) { "Incorrect host energy usage at timestamp 0" } }, + { assertEquals(2 * ((600 * 150.0) + (600 * 300.0)), monitor.hostEnergyUsages["H01"]?.sum()) { "Incorrect host energy usage" } }, + { assertEquals(2 * ((600 * 150.0) + (600 * 300.0)), monitor.energyUsages.sum()) { "Incorrect total energy usage" } }, + ) + } + + /** + * Scenario test 10: Two tasks running on CPU & GPU + * + * In this test, two tasks are scheduled at the same time that takes 10 minutes to run. One task purely uses CPU, one purely GPU. + */ + @Test + fun testScenario10() { + val workload: ArrayList = + arrayListOf( + createTestTask( + id = 0, + fragments = + arrayListOf( + TraceFragment(10 * 60 * 1000, 1000.0, 0.0), + ), + cpuCount = 1, + gpuCount = 0, + ), + createTestTask( + id = 1, + fragments = + arrayListOf( + TraceFragment(10 * 60 * 1000, 0.0, 1000.0), + ), + cpuCount = 0, + gpuCount = 1, + ), + ) + + val topology = createTopology("Gpus/single_gpu_no_vendor_no_memory.json") + val monitor = runTest(topology, workload) + + assertAll( + { assertEquals(10 * 60 * 1000, monitor.maxTimestamp) { "Total runtime incorrect" } }, + { assertEquals(((10 * 30000)).toLong(), monitor.hostCpuIdleTimes["H01"]?.sum()) { "CPU Idle time incorrect" } }, + { assertEquals(((10 * 30000)).toLong(), monitor.hostCpuActiveTimes["H01"]?.sum()) { "CPU Active time incorrect" } }, + { + assertEquals( + ((10 * 30000)).toLong(), + monitor.hostGpuIdleTimes["H01"]?.fold(0, { acc, iterator -> acc + iterator[0] }), + ) { "GPU Idle time incorrect" } + }, + { + assertEquals( + ((10 * 30000)).toLong(), + monitor.hostGpuActiveTimes["H01"]?.fold(0, { acc, iterator -> acc + iterator[0] }), + ) { "GPU Active time incorrect" } + }, + // double, as CPU and GPU both use power + { assertEquals(27000.0, monitor.hostEnergyUsages["H01"]?.get(0)) { "Incorrect host energy usage at timestamp 0" } }, + { assertEquals((600 * 150.0) + (600 * 300.0), monitor.hostEnergyUsages["H01"]?.sum()) { "Incorrect host energy usage" } }, + { assertEquals((600 * 150.0) + (600 * 300.0), monitor.energyUsages.sum()) { "Incorrect total energy usage" } }, + ) + } +} diff --git a/opendc-experiments/opendc-experiments-base/src/test/kotlin/org/opendc/experiments/base/TestingUtils.kt b/opendc-experiments/opendc-experiments-base/src/test/kotlin/org/opendc/experiments/base/TestingUtils.kt index f34160f7..d5e4c925 100644 --- a/opendc-experiments/opendc-experiments-base/src/test/kotlin/org/opendc/experiments/base/TestingUtils.kt +++ b/opendc-experiments/opendc-experiments-base/src/test/kotlin/org/opendc/experiments/base/TestingUtils.kt @@ -75,6 +75,8 @@ fun createTestTask( checkpointDuration: Long = 0L, checkpointIntervalScaling: Double = 1.0, scalingPolicy: ScalingPolicy = NoDelayScaling(), + parents: Set = emptySet(), + children: Set = emptySet(), ): Task { var usedResources = arrayOf() if (fragments.any { it.cpuUsage > 0.0 }) { @@ -89,8 +91,8 @@ fun createTestTask( name, LocalDateTime.parse(submissionTime).toInstant(ZoneOffset.UTC).toEpochMilli(), duration, - emptySet(), - emptySet(), + parents, + children, cpuCount, fragments.maxOf { it.cpuUsage }, 1800000.0, diff --git a/opendc-experiments/opendc-experiments-base/src/test/kotlin/org/opendc/experiments/base/WorkflowTest.kt b/opendc-experiments/opendc-experiments-base/src/test/kotlin/org/opendc/experiments/base/WorkflowTest.kt new file mode 100644 index 00000000..6e149d45 --- /dev/null +++ b/opendc-experiments/opendc-experiments-base/src/test/kotlin/org/opendc/experiments/base/WorkflowTest.kt @@ -0,0 +1,561 @@ +/* + * 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.base + +import org.junit.jupiter.api.Assertions.assertEquals +import org.junit.jupiter.api.Test +import org.junit.jupiter.api.assertAll +import org.opendc.compute.workload.Task +import org.opendc.simulator.compute.workload.trace.TraceFragment +import java.util.ArrayList + +/** + * An integration test suite for the Scenario experiments. + */ +class WorkflowTest { + /** + * Scenario test 1: simple workflow + * In this test, a simple workflow with 4 tasks is executed on a single host with 2 CPUs. + * The tasks are arranged in a diamond shape, where task 0 is the root task, tasks 1 and 2 are the children of task 0, + * and task 3 is the child of tasks 1 and 2. + * + * There should be no problems running the task, so the total runtime should be 30 min because 1 and 2 can be executed + * at the same time. + * + */ + @Test + fun testWorkflow1() { + val workload: ArrayList = + arrayListOf( + createTestTask( + id = 0, + fragments = + arrayListOf( + TraceFragment(10 * 60 * 1000, 1000.0), + ), + cpuCount = 1, + parents = emptySet(), + children = mutableSetOf(1, 2), + ), + createTestTask( + id = 1, + fragments = + arrayListOf( + TraceFragment(10 * 60 * 1000, 1000.0), + ), + cpuCount = 1, + parents = mutableSetOf(0), + children = mutableSetOf(3), + ), + createTestTask( + id = 2, + fragments = + arrayListOf( + TraceFragment(10 * 60 * 1000, 1000.0), + ), + cpuCount = 1, + parents = mutableSetOf(0), + children = mutableSetOf(3), + ), + createTestTask( + id = 3, + fragments = + arrayListOf( + TraceFragment(10 * 60 * 1000, 1000.0), + ), + cpuCount = 1, + parents = mutableSetOf(1, 2), + children = emptySet(), + ), + ) + + val topology = createTopology("single_2_2000.json") + + val monitor = runTest(topology, workload) + + assertAll( + { assertEquals(3 * 10 * 60 * 1000, monitor.maxTimestamp) { "Total runtime incorrect" } }, + { + assertEquals( + ((10 * 45000) + (10 * 30000) + (10 * 45000)).toLong(), + monitor.hostCpuIdleTimes["H01"]?.sum(), + ) { "Idle time incorrect" } + }, + { + assertEquals( + ((10 * 15000) + (10 * 30000) + (10 * 15000)).toLong(), + monitor.hostCpuActiveTimes["H01"]?.sum(), + ) { "Active time incorrect" } + }, + { assertEquals(7500.0, monitor.hostEnergyUsages["H01"]?.get(0)) { "Incorrect host energy usage at timestamp 0" } }, + { assertEquals(9000.0, monitor.hostEnergyUsages["H01"]?.get(10)) { "Incorrect host energy usage at timestamp 0" } }, + { assertEquals(7500.0, monitor.hostEnergyUsages["H01"]?.get(20)) { "Incorrect host energy usage at timestamp 0" } }, + { + assertEquals( + 600 * 125.0 + 600 * 150.0 + 600 * 125.0, + monitor.hostEnergyUsages["H01"]?.sum(), + ) { "Incorrect host energy usage" } + }, + { assertEquals(600 * 125.0 + 600 * 150.0 + 600 * 125.0, monitor.energyUsages.sum()) { "Incorrect total energy usage" } }, + ) + } + + /** + * Scenario test 2: simple workflow + * In this test, a simple workflow with 4 tasks is executed on a single host with 2 CPUs. + * The tasks are arranged in a diamond shape, where task 0 is the root task, tasks 1 and 2 are the children of task 0, + * and task 3 is the child of tasks 1 and 2. However, task 2 has a shorter duration than task 1. + * + * There should be no problems running the task, so the total runtime should still be 30 min because + * 3 can only be executed after 1 is finished. + * + */ + @Test + fun testWorkflow2() { + val workload: ArrayList = + arrayListOf( + createTestTask( + id = 0, + fragments = + arrayListOf( + TraceFragment(10 * 60 * 1000, 1000.0), + ), + cpuCount = 1, + parents = emptySet(), + children = mutableSetOf(1, 2), + ), + createTestTask( + id = 1, + fragments = + arrayListOf( + TraceFragment(10 * 60 * 1000, 1000.0), + ), + cpuCount = 1, + parents = mutableSetOf(0), + children = mutableSetOf(3), + ), + createTestTask( + id = 2, + fragments = + arrayListOf( + TraceFragment(5 * 60 * 1000, 1000.0), + ), + cpuCount = 1, + parents = mutableSetOf(0), + children = mutableSetOf(3), + ), + createTestTask( + id = 3, + fragments = + arrayListOf( + TraceFragment(10 * 60 * 1000, 1000.0), + ), + cpuCount = 1, + parents = mutableSetOf(1, 2), + children = emptySet(), + ), + ) + + val topology = createTopology("single_2_2000.json") + + val monitor = runTest(topology, workload) + + assertAll( + { assertEquals(3 * 10 * 60 * 1000, monitor.maxTimestamp) { "Total runtime incorrect" } }, + { + assertEquals( + ((10 * 45000) + (5 * 30000) + (15 * 45000)).toLong(), + monitor.hostCpuIdleTimes["H01"]?.sum(), + ) { "Idle time incorrect" } + }, + { + assertEquals( + ((10 * 15000) + (5 * 30000) + (15 * 15000)).toLong(), + monitor.hostCpuActiveTimes["H01"]?.sum(), + ) { "Active time incorrect" } + }, + { + assertEquals( + 7500.0, + monitor.hostEnergyUsages["H01"]?.get(0), + ) { "Incorrect host energy usage at timestamp 0" } + }, + { + assertEquals( + 9000.0, + monitor.hostEnergyUsages["H01"]?.get(10), + ) { "Incorrect host energy usage at timestamp 0" } + }, + { + assertEquals( + 7500.0, + monitor.hostEnergyUsages["H01"]?.get(20), + ) { "Incorrect host energy usage at timestamp 0" } + }, + { + assertEquals( + 600 * 125.0 + 300 * 150.0 + 900 * 125.0, + monitor.hostEnergyUsages["H01"]?.sum(), + ) { "Incorrect host energy usage" } + }, + { + assertEquals( + 600 * 125.0 + 300 * 150.0 + 900 * 125.0, + monitor.energyUsages.sum(), + ) { "Incorrect total energy usage" } + }, + ) + } + + /** + * Scenario test 3: simple workflow with unconnected extra task + * In this test, a simple workflow with 4 tasks is executed on a single host with 2 CPUs. + * However, there is also another task that is not connected to the workflow running. + * + * This means that the workflow cannot be parallelized as the extra task will take up one CPU for 40 minutes. + * + * The total runtime should therefore be 40 minutes. + */ + @Test + fun testWorkflow3() { + val workload: ArrayList = + arrayListOf( + createTestTask( + id = 0, + fragments = + arrayListOf( + TraceFragment(10 * 60 * 1000, 1000.0), + ), + cpuCount = 1, + parents = emptySet(), + children = mutableSetOf(1, 2), + ), + createTestTask( + id = 1, + fragments = + arrayListOf( + TraceFragment(10 * 60 * 1000, 1000.0), + ), + cpuCount = 1, + parents = mutableSetOf(0), + children = mutableSetOf(3), + ), + createTestTask( + id = 2, + fragments = + arrayListOf( + TraceFragment(10 * 60 * 1000, 1000.0), + ), + cpuCount = 1, + parents = mutableSetOf(0), + children = mutableSetOf(3), + ), + createTestTask( + id = 3, + fragments = + arrayListOf( + TraceFragment(10 * 60 * 1000, 1000.0), + ), + cpuCount = 1, + parents = mutableSetOf(1, 2), + children = emptySet(), + ), + createTestTask( + id = 5, + fragments = + arrayListOf( + TraceFragment(40 * 60 * 1000, 1000.0), + ), + cpuCount = 1, + ), + ) + + val topology = createTopology("single_2_2000.json") + + val monitor = runTest(topology, workload) + + assertAll( + { assertEquals(4 * 10 * 60 * 1000, monitor.maxTimestamp) { "Total runtime incorrect" } }, + { + assertEquals( + ((40 * 30000)).toLong(), + monitor.hostCpuIdleTimes["H01"]?.sum(), + ) { "Idle time incorrect" } + }, + { + assertEquals( + ((40 * 30000)).toLong(), + monitor.hostCpuActiveTimes["H01"]?.sum(), + ) { "Active time incorrect" } + }, + { + assertEquals( + 9000.0, + monitor.hostEnergyUsages["H01"]?.get(0), + ) { "Incorrect host energy usage at timestamp 0" } + }, + { + assertEquals( + 9000.0, + monitor.hostEnergyUsages["H01"]?.get(10), + ) { "Incorrect host energy usage at timestamp 0" } + }, + { + assertEquals( + 9000.0, + monitor.hostEnergyUsages["H01"]?.get(20), + ) { "Incorrect host energy usage at timestamp 0" } + }, + { + assertEquals( + 2400 * 150.0, + monitor.hostEnergyUsages["H01"]?.sum(), + ) { "Incorrect host energy usage" } + }, + { + assertEquals( + 2400 * 150.0, + monitor.energyUsages.sum(), + ) { "Incorrect total energy usage" } + }, + ) + } + + /** + * Scenario test 4: simple workflow with unconnected extra task + * In this test, a simple workflow with 4 tasks is executed on a single host with 2 CPUs. + * However, there is also another task that is not connected to the workflow running. + * + * This means that the workflow cannot be parallelized for the first 15 minutes as the extra task will take up one CPU. + * After that, the workflow can be parallelized as the extra task is finished. + * + * The total runtime should therefore be 35 minutes. + */ + @Test + fun testWorkflow4() { + val workload: ArrayList = + arrayListOf( + createTestTask( + id = 0, + fragments = + arrayListOf( + TraceFragment(10 * 60 * 1000, 1000.0), + ), + cpuCount = 1, + parents = emptySet(), + children = mutableSetOf(1, 2), + ), + createTestTask( + id = 1, + fragments = + arrayListOf( + TraceFragment(10 * 60 * 1000, 1000.0), + ), + cpuCount = 1, + parents = mutableSetOf(0), + children = mutableSetOf(3), + ), + createTestTask( + id = 2, + fragments = + arrayListOf( + TraceFragment(10 * 60 * 1000, 1000.0), + ), + cpuCount = 1, + parents = mutableSetOf(0), + children = mutableSetOf(3), + ), + createTestTask( + id = 3, + fragments = + arrayListOf( + TraceFragment(10 * 60 * 1000, 1000.0), + ), + cpuCount = 1, + parents = mutableSetOf(1, 2), + children = emptySet(), + ), + createTestTask( + id = 5, + fragments = + arrayListOf( + TraceFragment(15 * 60 * 1000, 1000.0), + ), + cpuCount = 1, + ), + ) + + val topology = createTopology("single_2_2000.json") + + val monitor = runTest(topology, workload) + + assertAll( + { assertEquals(35 * 60 * 1000, monitor.maxTimestamp) { "Total runtime incorrect" } }, + { + assertEquals( + ((20 * 30000) + (15 * 45000)).toLong(), + monitor.hostCpuIdleTimes["H01"]?.sum(), + ) { "Idle time incorrect" } + }, + { + assertEquals( + ((20 * 30000) + (15 * 15000)).toLong(), + monitor.hostCpuActiveTimes["H01"]?.sum(), + ) { "Active time incorrect" } + }, + { + assertEquals( + 9000.0, + monitor.hostEnergyUsages["H01"]?.get(0), + ) { "Incorrect host energy usage at timestamp 0" } + }, + { + assertEquals( + 9000.0, + monitor.hostEnergyUsages["H01"]?.get(10), + ) { "Incorrect host energy usage at timestamp 0" } + }, + { + assertEquals( + 7500.0, + monitor.hostEnergyUsages["H01"]?.get(20), + ) { "Incorrect host energy usage at timestamp 0" } + }, + { + assertEquals( + 1200 * 150.0 + 900 * 125.0, + monitor.hostEnergyUsages["H01"]?.sum(), + ) { "Incorrect host energy usage" } + }, + { + assertEquals( + 1200 * 150.0 + 900 * 125.0, + monitor.energyUsages.sum(), + ) { "Incorrect total energy usage" } + }, + ) + } + + /** + * Scenario test 3: workflow for which the first task cannot be scheduled. + * In this test, a simple workflow with 4 tasks is executed on a single host with 2 CPUs. + * However, the first task can not be scheduled on the host due to its high CPU requirement. + * The whole workflow is therefore terminated. + * + * The single unrelated task should still be executed + * + * The total runtime should therefore be 10 minutes. + */ + @Test + fun testWorkflow5() { + val workload: ArrayList = + arrayListOf( + createTestTask( + id = 0, + fragments = + arrayListOf( + TraceFragment(10 * 60 * 1000, 10000.0), + ), + cpuCount = 10, + parents = emptySet(), + children = mutableSetOf(1, 2), + ), + createTestTask( + id = 1, + fragments = + arrayListOf( + TraceFragment(10 * 60 * 1000, 1000.0), + ), + cpuCount = 1, + parents = mutableSetOf(0), + children = mutableSetOf(3), + ), + createTestTask( + id = 2, + fragments = + arrayListOf( + TraceFragment(10 * 60 * 1000, 1000.0), + ), + cpuCount = 1, + parents = mutableSetOf(0), + children = mutableSetOf(3), + ), + createTestTask( + id = 3, + fragments = + arrayListOf( + TraceFragment(10 * 60 * 1000, 1000.0), + ), + cpuCount = 1, + parents = mutableSetOf(1, 2), + children = emptySet(), + ), + createTestTask( + id = 5, + fragments = + arrayListOf( + TraceFragment(10 * 60 * 1000, 1000.0), + ), + cpuCount = 1, + ), + ) + + val topology = createTopology("single_2_2000.json") + + val monitor = runTest(topology, workload) + + assertAll( + { assertEquals(10 * 60 * 1000, monitor.maxTimestamp) { "Total runtime incorrect" } }, + { + assertEquals( + ((10 * 45000)).toLong(), + monitor.hostCpuIdleTimes["H01"]?.sum(), + ) { "Idle time incorrect" } + }, + { + assertEquals( + ((10 * 15000)).toLong(), + monitor.hostCpuActiveTimes["H01"]?.sum(), + ) { "Active time incorrect" } + }, + { + assertEquals( + 7500.0, + monitor.hostEnergyUsages["H01"]?.get(0), + ) { "Incorrect host energy usage at timestamp 0" } + }, + { + assertEquals( + 600 * 125.0, + monitor.hostEnergyUsages["H01"]?.sum(), + ) { "Incorrect host energy usage" } + }, + { + assertEquals( + 600 * 125.0, + monitor.energyUsages.sum(), + ) { "Incorrect total energy usage" } + }, + ) + } +} -- cgit v1.2.3