From b6acc74b38643615df02ef2131380c5e8eba00dd Mon Sep 17 00:00:00 2001 From: Fabian Mastenbroek Date: Wed, 9 Jun 2021 21:59:20 +0200 Subject: simulator: Add module for datacenter power components This change adds a new module for simulating power components in datacenters such as PDUs and UPSes. This module will serve as the basis for the power monitoring framework in OpenDC and will future integrate with the other simulation components (such as compute). --- .../kotlin/org/opendc/simulator/power/SimPdu.kt | 68 ++++++++++ .../org/opendc/simulator/power/SimPowerInlet.kt | 48 ++++++++ .../org/opendc/simulator/power/SimPowerOutlet.kt | 80 ++++++++++++ .../org/opendc/simulator/power/SimPowerSource.kt | 54 ++++++++ .../org/opendc/simulator/power/SimPduTest.kt | 108 ++++++++++++++++ .../opendc/simulator/power/SimPowerSourceTest.kt | 137 +++++++++++++++++++++ 6 files changed, 495 insertions(+) create mode 100644 opendc-simulator/opendc-simulator-power/src/main/kotlin/org/opendc/simulator/power/SimPdu.kt create mode 100644 opendc-simulator/opendc-simulator-power/src/main/kotlin/org/opendc/simulator/power/SimPowerInlet.kt create mode 100644 opendc-simulator/opendc-simulator-power/src/main/kotlin/org/opendc/simulator/power/SimPowerOutlet.kt create mode 100644 opendc-simulator/opendc-simulator-power/src/main/kotlin/org/opendc/simulator/power/SimPowerSource.kt create mode 100644 opendc-simulator/opendc-simulator-power/src/test/kotlin/org/opendc/simulator/power/SimPduTest.kt create mode 100644 opendc-simulator/opendc-simulator-power/src/test/kotlin/org/opendc/simulator/power/SimPowerSourceTest.kt (limited to 'opendc-simulator/opendc-simulator-power/src') diff --git a/opendc-simulator/opendc-simulator-power/src/main/kotlin/org/opendc/simulator/power/SimPdu.kt b/opendc-simulator/opendc-simulator-power/src/main/kotlin/org/opendc/simulator/power/SimPdu.kt new file mode 100644 index 00000000..365f3435 --- /dev/null +++ b/opendc-simulator/opendc-simulator-power/src/main/kotlin/org/opendc/simulator/power/SimPdu.kt @@ -0,0 +1,68 @@ +/* + * 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.power + +import org.opendc.simulator.resources.* + +/** + * A model of a Power Distribution Unit (PDU). + * + * @param interpreter The underlying [SimResourceInterpreter] to drive the simulation under the hood. + */ +public class SimPdu(interpreter: SimResourceInterpreter) : SimPowerInlet() { + /** + * The [SimResourceDistributor] that distributes the electricity over the PDU outlets. + */ + private val distributor = SimResourceDistributorMaxMin(interpreter) + + /** + * Create a new PDU outlet. + */ + public fun newOutlet(): Outlet = Outlet(distributor.newOutput()) + + override fun createConsumer(): SimResourceConsumer = distributor + + override fun toString(): String = "SimPdu" + + /** + * A PDU outlet. + */ + public class Outlet(private val provider: SimResourceProvider) : SimPowerOutlet(), AutoCloseable { + override fun onConnect(inlet: SimPowerInlet) { + provider.startConsumer(inlet.createConsumer()) + } + + override fun onDisconnect(inlet: SimPowerInlet) { + provider.cancel() + } + + /** + * Remove the outlet from the PDU. + */ + override fun close() { + provider.close() + } + + override fun toString(): String = "SimPdu.Outlet" + } +} diff --git a/opendc-simulator/opendc-simulator-power/src/main/kotlin/org/opendc/simulator/power/SimPowerInlet.kt b/opendc-simulator/opendc-simulator-power/src/main/kotlin/org/opendc/simulator/power/SimPowerInlet.kt new file mode 100644 index 00000000..0ac1f199 --- /dev/null +++ b/opendc-simulator/opendc-simulator-power/src/main/kotlin/org/opendc/simulator/power/SimPowerInlet.kt @@ -0,0 +1,48 @@ +/* + * 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.power + +import org.opendc.simulator.resources.SimResourceConsumer + +/** + * An abstract inlet that consumes electricity from a power outlet. + */ +public abstract class SimPowerInlet { + /** + * A flag to indicate that the inlet is currently connected to an outlet. + */ + public val isConnected: Boolean + get() = _outlet != null + + /** + * The [SimPowerOutlet] to which the inlet is connected. + */ + public val outlet: SimPowerOutlet? + get() = _outlet + internal var _outlet: SimPowerOutlet? = null + + /** + * Create a [SimResourceConsumer] which represents the consumption of electricity from the power outlet. + */ + public abstract fun createConsumer(): SimResourceConsumer +} diff --git a/opendc-simulator/opendc-simulator-power/src/main/kotlin/org/opendc/simulator/power/SimPowerOutlet.kt b/opendc-simulator/opendc-simulator-power/src/main/kotlin/org/opendc/simulator/power/SimPowerOutlet.kt new file mode 100644 index 00000000..72f52acc --- /dev/null +++ b/opendc-simulator/opendc-simulator-power/src/main/kotlin/org/opendc/simulator/power/SimPowerOutlet.kt @@ -0,0 +1,80 @@ +/* + * 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.power + +/** + * An abstract outlet that provides a source of electricity for datacenter components. + */ +public abstract class SimPowerOutlet { + /** + * A flag to indicate that the inlet is currently connected to an outlet. + */ + public val isConnected: Boolean + get() = _inlet != null + + /** + * The inlet that is connected to this outlet currently. + */ + public val inlet: SimPowerInlet? + get() = _inlet + private var _inlet: SimPowerInlet? = null + + /** + * Connect the specified power [inlet] to this outlet. + * + * @param inlet The inlet to connect to the outlet. + */ + public fun connect(inlet: SimPowerInlet) { + check(!isConnected) { "Outlet already connected" } + check(!inlet.isConnected) { "Inlet already connected" } + + _inlet = inlet + inlet._outlet = this + + onConnect(inlet) + } + + /** + * Disconnect the connected power outlet from this inlet + */ + public fun disconnect() { + val inlet = _inlet + if (inlet != null) { + _inlet = null + assert(inlet._outlet == this) { "Inlet state incorrect" } + inlet._outlet = null + + onDisconnect(inlet) + } + } + + /** + * This method is invoked when an inlet is connected to the outlet. + */ + protected abstract fun onConnect(inlet: SimPowerInlet) + + /** + * This method is invoked when an inlet is disconnected from the outlet. + */ + protected abstract fun onDisconnect(inlet: SimPowerInlet) +} diff --git a/opendc-simulator/opendc-simulator-power/src/main/kotlin/org/opendc/simulator/power/SimPowerSource.kt b/opendc-simulator/opendc-simulator-power/src/main/kotlin/org/opendc/simulator/power/SimPowerSource.kt new file mode 100644 index 00000000..3ef8ccc6 --- /dev/null +++ b/opendc-simulator/opendc-simulator-power/src/main/kotlin/org/opendc/simulator/power/SimPowerSource.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.simulator.power + +import org.opendc.simulator.resources.SimResourceInterpreter +import org.opendc.simulator.resources.SimResourceSource + +/** + * A [SimPowerOutlet] that represents a source of electricity. + * + * @param interpreter The underlying [SimResourceInterpreter] to drive the simulation under the hood. + */ +public class SimPowerSource(interpreter: SimResourceInterpreter, public val capacity: Double) : SimPowerOutlet() { + /** + * The resource source that drives this power source. + */ + private val source = SimResourceSource(capacity, interpreter) + + /** + * The power draw at this instant. + */ + public val powerDraw: Double + get() = source.speed + + override fun onConnect(inlet: SimPowerInlet) { + source.startConsumer(inlet.createConsumer()) + } + + override fun onDisconnect(inlet: SimPowerInlet) { + source.cancel() + } + + override fun toString(): String = "SimPowerSource" +} diff --git a/opendc-simulator/opendc-simulator-power/src/test/kotlin/org/opendc/simulator/power/SimPduTest.kt b/opendc-simulator/opendc-simulator-power/src/test/kotlin/org/opendc/simulator/power/SimPduTest.kt new file mode 100644 index 00000000..cef9a97a --- /dev/null +++ b/opendc-simulator/opendc-simulator-power/src/test/kotlin/org/opendc/simulator/power/SimPduTest.kt @@ -0,0 +1,108 @@ +/* + * 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.power + +import io.mockk.spyk +import io.mockk.verify +import org.junit.jupiter.api.Assertions.assertEquals +import org.junit.jupiter.api.Test +import org.junit.jupiter.api.assertThrows +import org.opendc.simulator.core.runBlockingSimulation +import org.opendc.simulator.resources.SimResourceConsumer +import org.opendc.simulator.resources.SimResourceEvent +import org.opendc.simulator.resources.SimResourceInterpreter +import org.opendc.simulator.resources.consumer.SimWorkConsumer + +/** + * Test suite for the [SimPdu] class. + */ +internal class SimPduTest { + @Test + fun testZeroOutlets() = runBlockingSimulation { + val interpreter = SimResourceInterpreter(coroutineContext, clock) + val source = SimPowerSource(interpreter, capacity = 100.0) + val pdu = SimPdu(interpreter) + source.connect(pdu) + + assertEquals(0.0, source.powerDraw) + } + + @Test + fun testSingleOutlet() = runBlockingSimulation { + val interpreter = SimResourceInterpreter(coroutineContext, clock) + val source = SimPowerSource(interpreter, capacity = 100.0) + val pdu = SimPdu(interpreter) + source.connect(pdu) + pdu.newOutlet().connect(SimpleInlet()) + assertEquals(50.0, source.powerDraw) + } + + @Test + fun testDoubleOutlet() = runBlockingSimulation { + val interpreter = SimResourceInterpreter(coroutineContext, clock) + val source = SimPowerSource(interpreter, capacity = 100.0) + val pdu = SimPdu(interpreter) + source.connect(pdu) + + pdu.newOutlet().connect(SimpleInlet()) + pdu.newOutlet().connect(SimpleInlet()) + + assertEquals(100.0, source.powerDraw) + } + + @Test + fun testDisconnect() = runBlockingSimulation { + val interpreter = SimResourceInterpreter(coroutineContext, clock) + val source = SimPowerSource(interpreter, capacity = 100.0) + val pdu = SimPdu(interpreter) + source.connect(pdu) + val consumer = spyk(SimWorkConsumer(100.0, utilization = 1.0)) + val inlet = object : SimPowerInlet() { + override fun createConsumer(): SimResourceConsumer = consumer + } + + val outlet = pdu.newOutlet() + outlet.connect(inlet) + outlet.disconnect() + + verify { consumer.onEvent(any(), SimResourceEvent.Exit) } + } + + @Test + fun testOutletClose() = runBlockingSimulation { + val interpreter = SimResourceInterpreter(coroutineContext, clock) + val source = SimPowerSource(interpreter, capacity = 100.0) + val pdu = SimPdu(interpreter) + source.connect(pdu) + val outlet = pdu.newOutlet() + outlet.close() + + assertThrows { + outlet.connect(SimpleInlet()) + } + } + + class SimpleInlet : SimPowerInlet() { + override fun createConsumer(): SimResourceConsumer = SimWorkConsumer(100.0, utilization = 0.5) + } +} diff --git a/opendc-simulator/opendc-simulator-power/src/test/kotlin/org/opendc/simulator/power/SimPowerSourceTest.kt b/opendc-simulator/opendc-simulator-power/src/test/kotlin/org/opendc/simulator/power/SimPowerSourceTest.kt new file mode 100644 index 00000000..f3829ba1 --- /dev/null +++ b/opendc-simulator/opendc-simulator-power/src/test/kotlin/org/opendc/simulator/power/SimPowerSourceTest.kt @@ -0,0 +1,137 @@ +/* + * 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.power + +import io.mockk.every +import io.mockk.mockk +import io.mockk.spyk +import io.mockk.verify +import org.junit.jupiter.api.Assertions.* +import org.junit.jupiter.api.Test +import org.junit.jupiter.api.assertDoesNotThrow +import org.junit.jupiter.api.assertThrows +import org.opendc.simulator.core.runBlockingSimulation +import org.opendc.simulator.resources.SimResourceConsumer +import org.opendc.simulator.resources.SimResourceEvent +import org.opendc.simulator.resources.SimResourceInterpreter +import org.opendc.simulator.resources.consumer.SimWorkConsumer + +/** + * Test suite for the [SimPowerSource] + */ +internal class SimPowerSourceTest { + @Test + fun testInitialState() = runBlockingSimulation { + val interpreter = SimResourceInterpreter(coroutineContext, clock) + val source = SimPowerSource(interpreter, capacity = 100.0) + + assertFalse(source.isConnected) + assertNull(source.inlet) + assertEquals(100.0, source.capacity) + } + + @Test + fun testDisconnectIdempotent() = runBlockingSimulation { + val interpreter = SimResourceInterpreter(coroutineContext, clock) + val source = SimPowerSource(interpreter, capacity = 100.0) + + assertDoesNotThrow { source.disconnect() } + assertFalse(source.isConnected) + } + + @Test + fun testConnect() = runBlockingSimulation { + val interpreter = SimResourceInterpreter(coroutineContext, clock) + val source = SimPowerSource(interpreter, capacity = 100.0) + val inlet = SimpleInlet() + + source.connect(inlet) + + assertTrue(source.isConnected) + assertEquals(inlet, source.inlet) + assertTrue(inlet.isConnected) + assertEquals(source, inlet.outlet) + assertEquals(100.0, source.powerDraw) + } + + @Test + fun testDisconnect() = runBlockingSimulation { + val interpreter = SimResourceInterpreter(coroutineContext, clock) + val source = SimPowerSource(interpreter, capacity = 100.0) + val consumer = spyk(SimWorkConsumer(100.0, utilization = 1.0)) + val inlet = object : SimPowerInlet() { + override fun createConsumer(): SimResourceConsumer = consumer + } + + source.connect(inlet) + source.disconnect() + + verify { consumer.onEvent(any(), SimResourceEvent.Exit) } + } + + @Test + fun testDisconnectAssertion() = runBlockingSimulation { + val interpreter = SimResourceInterpreter(coroutineContext, clock) + val source = SimPowerSource(interpreter, capacity = 100.0) + val inlet = mockk(relaxUnitFun = true) + every { inlet.isConnected } returns false + every { inlet._outlet } returns null + every { inlet.createConsumer() } returns SimWorkConsumer(100.0, utilization = 1.0) + + source.connect(inlet) + + assertThrows { + source.disconnect() + } + } + + @Test + fun testOutletAlreadyConnected() = runBlockingSimulation { + val interpreter = SimResourceInterpreter(coroutineContext, clock) + val source = SimPowerSource(interpreter, capacity = 100.0) + val inlet = SimpleInlet() + + source.connect(inlet) + assertThrows { + source.connect(SimpleInlet()) + } + + assertEquals(inlet, source.inlet) + } + + @Test + fun testInletAlreadyConnected() = runBlockingSimulation { + val interpreter = SimResourceInterpreter(coroutineContext, clock) + val source = SimPowerSource(interpreter, capacity = 100.0) + val inlet = mockk(relaxUnitFun = true) + every { inlet.isConnected } returns true + + assertThrows { + source.connect(inlet) + } + } + + class SimpleInlet : SimPowerInlet() { + override fun createConsumer(): SimResourceConsumer = SimWorkConsumer(100.0, utilization = 1.0) + } +} -- cgit v1.2.3 From f0c0c6d45165ad0ce398ec7300b11bf77c7ae5a6 Mon Sep 17 00:00:00 2001 From: Hongyu He Date: Thu, 10 Jun 2021 13:27:56 +0200 Subject: simulator: Add power loss to SimPdu This change adds a model for power loss to the Power Distribution Unit (PDU) model in OpenDC. --- .../kotlin/org/opendc/simulator/power/SimPdu.kt | 40 ++++++++++++++++++++-- .../org/opendc/simulator/power/SimPduTest.kt | 12 +++++++ 2 files changed, 50 insertions(+), 2 deletions(-) (limited to 'opendc-simulator/opendc-simulator-power/src') diff --git a/opendc-simulator/opendc-simulator-power/src/main/kotlin/org/opendc/simulator/power/SimPdu.kt b/opendc-simulator/opendc-simulator-power/src/main/kotlin/org/opendc/simulator/power/SimPdu.kt index 365f3435..ed3175c7 100644 --- a/opendc-simulator/opendc-simulator-power/src/main/kotlin/org/opendc/simulator/power/SimPdu.kt +++ b/opendc-simulator/opendc-simulator-power/src/main/kotlin/org/opendc/simulator/power/SimPdu.kt @@ -28,8 +28,14 @@ import org.opendc.simulator.resources.* * A model of a Power Distribution Unit (PDU). * * @param interpreter The underlying [SimResourceInterpreter] to drive the simulation under the hood. + * @param idlePower The idle power consumption of the PDU independent of the load on the PDU. + * @param lossCoefficient The coefficient for the power loss of the PDU proportional to the square load. */ -public class SimPdu(interpreter: SimResourceInterpreter) : SimPowerInlet() { +public class SimPdu( + interpreter: SimResourceInterpreter, + public val idlePower: Double = 0.0, + public val lossCoefficient: Double = 0.0, +) : SimPowerInlet() { /** * The [SimResourceDistributor] that distributes the electricity over the PDU outlets. */ @@ -40,10 +46,40 @@ public class SimPdu(interpreter: SimResourceInterpreter) : SimPowerInlet() { */ public fun newOutlet(): Outlet = Outlet(distributor.newOutput()) - override fun createConsumer(): SimResourceConsumer = distributor + override fun createConsumer(): SimResourceConsumer = object : SimResourceConsumer by distributor { + override fun onNext(ctx: SimResourceContext): SimResourceCommand { + return when (val cmd = distributor.onNext(ctx)) { + is SimResourceCommand.Consume -> { + val duration = cmd.work / cmd.limit + val loss = computePowerLoss(cmd.limit) + val newLimit = cmd.limit + loss + + SimResourceCommand.Consume(duration * newLimit, newLimit, cmd.deadline) + } + is SimResourceCommand.Idle -> { + val loss = computePowerLoss(0.0) + if (loss > 0.0) + SimResourceCommand.Consume(Double.POSITIVE_INFINITY, loss, cmd.deadline) + else + cmd + } + else -> cmd + } + } + + override fun toString(): String = "SimPdu.Consumer" + } override fun toString(): String = "SimPdu" + /** + * Compute the power loss that occurs in the PDU. + */ + private fun computePowerLoss(load: Double): Double { + // See https://download.schneider-electric.com/files?p_Doc_Ref=SPD_NRAN-66CK3D_EN + return idlePower + lossCoefficient * (load * load) + } + /** * A PDU outlet. */ diff --git a/opendc-simulator/opendc-simulator-power/src/test/kotlin/org/opendc/simulator/power/SimPduTest.kt b/opendc-simulator/opendc-simulator-power/src/test/kotlin/org/opendc/simulator/power/SimPduTest.kt index cef9a97a..b7f51ad3 100644 --- a/opendc-simulator/opendc-simulator-power/src/test/kotlin/org/opendc/simulator/power/SimPduTest.kt +++ b/opendc-simulator/opendc-simulator-power/src/test/kotlin/org/opendc/simulator/power/SimPduTest.kt @@ -54,6 +54,7 @@ internal class SimPduTest { val pdu = SimPdu(interpreter) source.connect(pdu) pdu.newOutlet().connect(SimpleInlet()) + assertEquals(50.0, source.powerDraw) } @@ -88,6 +89,17 @@ internal class SimPduTest { verify { consumer.onEvent(any(), SimResourceEvent.Exit) } } + @Test + fun testLoss() = runBlockingSimulation { + val interpreter = SimResourceInterpreter(coroutineContext, clock) + val source = SimPowerSource(interpreter, capacity = 100.0) + // https://download.schneider-electric.com/files?p_Doc_Ref=SPD_NRAN-66CK3D_EN + val pdu = SimPdu(interpreter, idlePower = 0.015, lossCoefficient = 0.015) + source.connect(pdu) + pdu.newOutlet().connect(SimpleInlet()) + assertEquals(87.515, source.powerDraw, 0.01) + } + @Test fun testOutletClose() = runBlockingSimulation { val interpreter = SimResourceInterpreter(coroutineContext, clock) -- cgit v1.2.3 From 885137dd79f76a63aee1bcaecbc0c9e9dec80d3a Mon Sep 17 00:00:00 2001 From: Hongyu He Date: Fri, 11 Jun 2021 17:42:47 +0200 Subject: simulator: Add model for UPS This change adds a new model for the UPS to the OpenDC simulator power subsystem. --- .../kotlin/org/opendc/simulator/power/SimPdu.kt | 4 +- .../kotlin/org/opendc/simulator/power/SimUps.kt | 107 +++++++++++++++++++++ .../org/opendc/simulator/power/SimPduTest.kt | 4 +- .../org/opendc/simulator/power/SimUpsTest.kt | 102 ++++++++++++++++++++ 4 files changed, 213 insertions(+), 4 deletions(-) create mode 100644 opendc-simulator/opendc-simulator-power/src/main/kotlin/org/opendc/simulator/power/SimUps.kt create mode 100644 opendc-simulator/opendc-simulator-power/src/test/kotlin/org/opendc/simulator/power/SimUpsTest.kt (limited to 'opendc-simulator/opendc-simulator-power/src') diff --git a/opendc-simulator/opendc-simulator-power/src/main/kotlin/org/opendc/simulator/power/SimPdu.kt b/opendc-simulator/opendc-simulator-power/src/main/kotlin/org/opendc/simulator/power/SimPdu.kt index ed3175c7..11034a57 100644 --- a/opendc-simulator/opendc-simulator-power/src/main/kotlin/org/opendc/simulator/power/SimPdu.kt +++ b/opendc-simulator/opendc-simulator-power/src/main/kotlin/org/opendc/simulator/power/SimPdu.kt @@ -33,8 +33,8 @@ import org.opendc.simulator.resources.* */ public class SimPdu( interpreter: SimResourceInterpreter, - public val idlePower: Double = 0.0, - public val lossCoefficient: Double = 0.0, + private val idlePower: Double = 0.0, + private val lossCoefficient: Double = 0.0, ) : SimPowerInlet() { /** * The [SimResourceDistributor] that distributes the electricity over the PDU outlets. diff --git a/opendc-simulator/opendc-simulator-power/src/main/kotlin/org/opendc/simulator/power/SimUps.kt b/opendc-simulator/opendc-simulator-power/src/main/kotlin/org/opendc/simulator/power/SimUps.kt new file mode 100644 index 00000000..f9431d21 --- /dev/null +++ b/opendc-simulator/opendc-simulator-power/src/main/kotlin/org/opendc/simulator/power/SimUps.kt @@ -0,0 +1,107 @@ +/* + * 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.power + +import org.opendc.simulator.resources.* + +/** + * A model of an Uninterruptible Power Supply (UPS). + * + * This model aggregates multiple power sources into a single source in order to ensure that power is always available. + * + * @param interpreter The underlying [SimResourceInterpreter] to drive the simulation under the hood. + * @param idlePower The idle power consumption of the UPS independent of the load. + * @param lossCoefficient The coefficient for the power loss of the UPS proportional to the load. + */ +public class SimUps( + interpreter: SimResourceInterpreter, + private val idlePower: Double = 0.0, + private val lossCoefficient: Double = 0.0, +) : SimPowerOutlet() { + /** + * The resource aggregator used to combine the input sources. + */ + private val aggregator = SimResourceAggregatorMaxMin(interpreter) + + /** + * Create a new UPS outlet. + */ + public fun newInlet(): SimPowerInlet { + val forward = SimResourceForwarder(isCoupled = true) + aggregator.addInput(forward) + return Inlet(forward) + } + + override fun onConnect(inlet: SimPowerInlet) { + val consumer = inlet.createConsumer() + aggregator.startConsumer(object : SimResourceConsumer by consumer { + override fun onNext(ctx: SimResourceContext): SimResourceCommand { + return when (val cmd = consumer.onNext(ctx)) { + is SimResourceCommand.Consume -> { + val duration = cmd.work / cmd.limit + val loss = computePowerLoss(cmd.limit) + val newLimit = cmd.limit + loss + + SimResourceCommand.Consume(duration * newLimit, newLimit, cmd.deadline) + } + is SimResourceCommand.Idle -> { + val loss = computePowerLoss(0.0) + if (loss > 0.0) + SimResourceCommand.Consume(Double.POSITIVE_INFINITY, loss, cmd.deadline) + else + cmd + } + else -> cmd + } + } + }) + } + + override fun onDisconnect(inlet: SimPowerInlet) { + aggregator.cancel() + } + + /** + * Compute the power loss that occurs in the UPS. + */ + private fun computePowerLoss(load: Double): Double { + // See https://download.schneider-electric.com/files?p_Doc_Ref=SPD_NRAN-66CK3D_EN + return idlePower + lossCoefficient * load + } + + /** + * A UPS inlet. + */ + public inner class Inlet(private val forwarder: SimResourceTransformer) : SimPowerInlet(), AutoCloseable { + override fun createConsumer(): SimResourceConsumer = forwarder + + /** + * Remove the inlet from the PSU. + */ + override fun close() { + forwarder.close() + } + + override fun toString(): String = "SimPsu.Inlet" + } +} diff --git a/opendc-simulator/opendc-simulator-power/src/test/kotlin/org/opendc/simulator/power/SimPduTest.kt b/opendc-simulator/opendc-simulator-power/src/test/kotlin/org/opendc/simulator/power/SimPduTest.kt index b7f51ad3..17a174b7 100644 --- a/opendc-simulator/opendc-simulator-power/src/test/kotlin/org/opendc/simulator/power/SimPduTest.kt +++ b/opendc-simulator/opendc-simulator-power/src/test/kotlin/org/opendc/simulator/power/SimPduTest.kt @@ -94,10 +94,10 @@ internal class SimPduTest { val interpreter = SimResourceInterpreter(coroutineContext, clock) val source = SimPowerSource(interpreter, capacity = 100.0) // https://download.schneider-electric.com/files?p_Doc_Ref=SPD_NRAN-66CK3D_EN - val pdu = SimPdu(interpreter, idlePower = 0.015, lossCoefficient = 0.015) + val pdu = SimPdu(interpreter, idlePower = 1.5, lossCoefficient = 0.015) source.connect(pdu) pdu.newOutlet().connect(SimpleInlet()) - assertEquals(87.515, source.powerDraw, 0.01) + assertEquals(89.0, source.powerDraw, 0.01) } @Test diff --git a/opendc-simulator/opendc-simulator-power/src/test/kotlin/org/opendc/simulator/power/SimUpsTest.kt b/opendc-simulator/opendc-simulator-power/src/test/kotlin/org/opendc/simulator/power/SimUpsTest.kt new file mode 100644 index 00000000..8d5fa857 --- /dev/null +++ b/opendc-simulator/opendc-simulator-power/src/test/kotlin/org/opendc/simulator/power/SimUpsTest.kt @@ -0,0 +1,102 @@ +/* + * 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.power + +import io.mockk.spyk +import io.mockk.verify +import org.junit.jupiter.api.Assertions.assertAll +import org.junit.jupiter.api.Assertions.assertEquals +import org.junit.jupiter.api.Test +import org.opendc.simulator.core.runBlockingSimulation +import org.opendc.simulator.resources.SimResourceConsumer +import org.opendc.simulator.resources.SimResourceEvent +import org.opendc.simulator.resources.SimResourceInterpreter +import org.opendc.simulator.resources.consumer.SimWorkConsumer + +/** + * Test suite for the [SimUps] class. + */ +internal class SimUpsTest { + @Test + fun testSingleInlet() = runBlockingSimulation { + val interpreter = SimResourceInterpreter(coroutineContext, clock) + val source = SimPowerSource(interpreter, capacity = 100.0) + val ups = SimUps(interpreter) + source.connect(ups.newInlet()) + ups.connect(SimpleInlet()) + + assertEquals(50.0, source.powerDraw) + } + + @Test + fun testDoubleInlet() = runBlockingSimulation { + val interpreter = SimResourceInterpreter(coroutineContext, clock) + val source1 = SimPowerSource(interpreter, capacity = 100.0) + val source2 = SimPowerSource(interpreter, capacity = 100.0) + val ups = SimUps(interpreter) + source1.connect(ups.newInlet()) + source2.connect(ups.newInlet()) + + ups.connect(SimpleInlet()) + + assertAll( + { assertEquals(50.0, source1.powerDraw) }, + { assertEquals(50.0, source2.powerDraw) } + ) + } + + @Test + fun testLoss() = runBlockingSimulation { + val interpreter = SimResourceInterpreter(coroutineContext, clock) + val source = SimPowerSource(interpreter, capacity = 100.0) + // https://download.schneider-electric.com/files?p_Doc_Ref=SPD_NRAN-66CK3D_EN + val ups = SimUps(interpreter, idlePower = 4.0, lossCoefficient = 0.05) + source.connect(ups.newInlet()) + ups.connect(SimpleInlet()) + + assertEquals(56.5, source.powerDraw) + } + + @Test + fun testDisconnect() = runBlockingSimulation { + val interpreter = SimResourceInterpreter(coroutineContext, clock) + val source1 = SimPowerSource(interpreter, capacity = 100.0) + val source2 = SimPowerSource(interpreter, capacity = 100.0) + val ups = SimUps(interpreter) + source1.connect(ups.newInlet()) + source2.connect(ups.newInlet()) + val consumer = spyk(SimWorkConsumer(100.0, utilization = 1.0)) + val inlet = object : SimPowerInlet() { + override fun createConsumer(): SimResourceConsumer = consumer + } + + ups.connect(inlet) + ups.disconnect() + + verify { consumer.onEvent(any(), SimResourceEvent.Exit) } + } + + class SimpleInlet : SimPowerInlet() { + override fun createConsumer(): SimResourceConsumer = SimWorkConsumer(100.0, utilization = 0.5) + } +} -- cgit v1.2.3