diff options
Diffstat (limited to 'opendc-simulator')
172 files changed, 10314 insertions, 8912 deletions
diff --git a/opendc-simulator/opendc-simulator-compute/src/jmh/kotlin/org/opendc/simulator/compute/SimMachineBenchmarks.kt b/opendc-simulator/opendc-simulator-compute/src/jmh/kotlin/org/opendc/simulator/compute/SimMachineBenchmarks.kt index 220b97cc..ec032070 100644 --- a/opendc-simulator/opendc-simulator-compute/src/jmh/kotlin/org/opendc/simulator/compute/SimMachineBenchmarks.kt +++ b/opendc-simulator/opendc-simulator-compute/src/jmh/kotlin/org/opendc/simulator/compute/SimMachineBenchmarks.kt @@ -29,12 +29,9 @@ import org.opendc.simulator.compute.model.MachineModel import org.opendc.simulator.compute.model.MemoryUnit import org.opendc.simulator.compute.model.ProcessingNode import org.opendc.simulator.compute.model.ProcessingUnit -import org.opendc.simulator.compute.power.ConstantPowerModel -import org.opendc.simulator.compute.power.SimplePowerDriver import org.opendc.simulator.compute.workload.SimTrace -import org.opendc.simulator.compute.workload.SimTraceWorkload -import org.opendc.simulator.flow.FlowEngine -import org.opendc.simulator.flow.mux.FlowMultiplexerFactory +import org.opendc.simulator.flow2.FlowEngine +import org.opendc.simulator.flow2.mux.FlowMultiplexerFactory import org.opendc.simulator.kotlin.runSimulation import org.openjdk.jmh.annotations.Benchmark import org.openjdk.jmh.annotations.Fork @@ -60,14 +57,14 @@ class SimMachineBenchmarks { val cpuNode = ProcessingNode("Intel", "Xeon", "amd64", 2) machineModel = MachineModel( - cpus = List(cpuNode.coreCount) { ProcessingUnit(cpuNode, it, 1000.0) }, - memory = List(4) { MemoryUnit("Crucial", "MTA18ASF4G72AZ-3G2B1", 3200.0, 32_000) } + /*cpus*/ List(cpuNode.coreCount) { ProcessingUnit(cpuNode, it, 1000.0) }, + /*memory*/ List(4) { MemoryUnit("Crucial", "MTA18ASF4G72AZ-3G2B1", 3200.0, 32_000) } ) val random = ThreadLocalRandom.current() val builder = SimTrace.builder() - repeat(10000) { - val timestamp = it.toLong() + repeat(1000000) { + val timestamp = it.toLong() * 1000 val deadline = timestamp + 1000 builder.add(deadline, random.nextDouble(0.0, 4500.0), 1) } @@ -77,29 +74,27 @@ class SimMachineBenchmarks { @Benchmark fun benchmarkBareMetal() { return runSimulation { - val engine = FlowEngine(coroutineContext, clock) - val machine = SimBareMetalMachine( - engine, - machineModel, - SimplePowerDriver(ConstantPowerModel(0.0)) - ) - return@runSimulation machine.runWorkload(SimTraceWorkload(trace)) + val engine = FlowEngine.create(coroutineContext, clock) + val graph = engine.newGraph() + val machine = SimBareMetalMachine.create(graph, machineModel) + return@runSimulation machine.runWorkload(trace.createWorkload(0)) } } @Benchmark fun benchmarkSpaceSharedHypervisor() { return runSimulation { - val engine = FlowEngine(coroutineContext, clock) - val machine = SimBareMetalMachine(engine, machineModel, SimplePowerDriver(ConstantPowerModel(0.0))) - val hypervisor = SimHypervisor(engine, FlowMultiplexerFactory.forwardingMultiplexer(), SplittableRandom(1), null) + val engine = FlowEngine.create(coroutineContext, clock) + val graph = engine.newGraph() + val machine = SimBareMetalMachine.create(graph, machineModel) + val hypervisor = SimHypervisor.create(FlowMultiplexerFactory.forwardingMultiplexer(), SplittableRandom(1)) launch { machine.runWorkload(hypervisor) } val vm = hypervisor.newMachine(machineModel) try { - return@runSimulation vm.runWorkload(SimTraceWorkload(trace)) + return@runSimulation vm.runWorkload(trace.createWorkload(0)) } finally { vm.cancel() machine.cancel() @@ -110,16 +105,17 @@ class SimMachineBenchmarks { @Benchmark fun benchmarkFairShareHypervisorSingle() { return runSimulation { - val engine = FlowEngine(coroutineContext, clock) - val machine = SimBareMetalMachine(engine, machineModel, SimplePowerDriver(ConstantPowerModel(0.0))) - val hypervisor = SimHypervisor(engine, FlowMultiplexerFactory.maxMinMultiplexer(), SplittableRandom(1), null) + val engine = FlowEngine.create(coroutineContext, clock) + val graph = engine.newGraph() + val machine = SimBareMetalMachine.create(graph, machineModel) + val hypervisor = SimHypervisor.create(FlowMultiplexerFactory.maxMinMultiplexer(), SplittableRandom(1)) launch { machine.runWorkload(hypervisor) } val vm = hypervisor.newMachine(machineModel) try { - return@runSimulation vm.runWorkload(SimTraceWorkload(trace)) + return@runSimulation vm.runWorkload(trace.createWorkload(0)) } finally { vm.cancel() machine.cancel() @@ -130,9 +126,10 @@ class SimMachineBenchmarks { @Benchmark fun benchmarkFairShareHypervisorDouble() { return runSimulation { - val engine = FlowEngine(coroutineContext, clock) - val machine = SimBareMetalMachine(engine, machineModel, SimplePowerDriver(ConstantPowerModel(0.0))) - val hypervisor = SimHypervisor(engine, FlowMultiplexerFactory.maxMinMultiplexer(), SplittableRandom(1), null) + val engine = FlowEngine.create(coroutineContext, clock) + val graph = engine.newGraph() + val machine = SimBareMetalMachine.create(graph, machineModel) + val hypervisor = SimHypervisor.create(FlowMultiplexerFactory.maxMinMultiplexer(), SplittableRandom(1)) launch { machine.runWorkload(hypervisor) } @@ -142,7 +139,7 @@ class SimMachineBenchmarks { launch { try { - vm.runWorkload(SimTraceWorkload(trace)) + vm.runWorkload(trace.createWorkload(0)) } finally { machine.cancel() } diff --git a/opendc-simulator/opendc-simulator-compute/src/main/java/org/opendc/simulator/compute/SimAbstractMachine.java b/opendc-simulator/opendc-simulator-compute/src/main/java/org/opendc/simulator/compute/SimAbstractMachine.java new file mode 100644 index 00000000..cf5aed03 --- /dev/null +++ b/opendc-simulator/opendc-simulator-compute/src/main/java/org/opendc/simulator/compute/SimAbstractMachine.java @@ -0,0 +1,327 @@ +/* + * Copyright (c) 2022 AtLarge Research + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ + +package org.opendc.simulator.compute; + +import java.util.HashMap; +import java.util.List; +import java.util.Map; +import org.opendc.simulator.compute.device.SimNetworkAdapter; +import org.opendc.simulator.compute.model.MachineModel; +import org.opendc.simulator.compute.model.MemoryUnit; +import org.opendc.simulator.compute.workload.SimWorkload; +import org.opendc.simulator.flow2.FlowGraph; +import org.opendc.simulator.flow2.Inlet; +import org.opendc.simulator.flow2.Outlet; +import org.opendc.simulator.flow2.sink.SimpleFlowSink; +import org.opendc.simulator.flow2.util.FlowTransformer; +import org.opendc.simulator.flow2.util.FlowTransforms; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +/** + * Abstract implementation of the {@link SimMachine} interface. + */ +public abstract class SimAbstractMachine implements SimMachine { + private static final Logger LOGGER = LoggerFactory.getLogger(SimAbstractMachine.class); + private final MachineModel model; + + private Context activeContext; + + /** + * Construct a {@link SimAbstractMachine} instance. + * + * @param model The model of the machine. + */ + public SimAbstractMachine(MachineModel model) { + this.model = model; + } + + @Override + public final MachineModel getModel() { + return model; + } + + @Override + public final SimMachineContext startWorkload(SimWorkload workload, Map<String, Object> meta) { + if (activeContext != null) { + throw new IllegalStateException("A machine cannot run multiple workloads concurrently"); + } + + final Context ctx = createContext(workload, new HashMap<>(meta)); + ctx.start(); + return ctx; + } + + @Override + public final void cancel() { + final Context context = activeContext; + if (context != null) { + context.shutdown(); + } + } + + /** + * Construct a new {@link Context} instance representing the active execution. + * + * @param workload The workload to start on the machine. + * @param meta The metadata to pass to the workload. + */ + protected abstract Context createContext(SimWorkload workload, Map<String, Object> meta); + + /** + * Return the active {@link Context} instance (if any). + */ + protected Context getActiveContext() { + return activeContext; + } + + /** + * The execution context in which the workload runs. + */ + public abstract static class Context implements SimMachineContext { + private final SimAbstractMachine machine; + private final SimWorkload workload; + private final Map<String, Object> meta; + private boolean isClosed; + + /** + * Construct a new {@link Context} instance. + * + * @param machine The {@link SimAbstractMachine} to which the context belongs. + * @param workload The {@link SimWorkload} to which the context belongs. + * @param meta The metadata passed to the context. + */ + public Context(SimAbstractMachine machine, SimWorkload workload, Map<String, Object> meta) { + this.machine = machine; + this.workload = workload; + this.meta = meta; + } + + @Override + public final Map<String, Object> getMeta() { + return meta; + } + + @Override + public final void shutdown() { + if (isClosed) { + return; + } + + isClosed = true; + final SimAbstractMachine machine = this.machine; + assert machine.activeContext == this : "Invariant violation: multiple contexts active for a single machine"; + machine.activeContext = null; + + // Cancel all the resources associated with the machine + doCancel(); + + try { + workload.onStop(this); + } catch (Exception cause) { + LOGGER.warn("Workload failed during onStop callback", cause); + } + } + + /** + * Start this context. + */ + final void start() { + try { + machine.activeContext = this; + workload.onStart(this); + } catch (Exception cause) { + LOGGER.warn("Workload failed during onStart callback", cause); + shutdown(); + } + } + + /** + * Run the stop procedures for the resources associated with the machine. + */ + protected void doCancel() { + final FlowGraph graph = getMemory().getInput().getGraph(); + + for (SimProcessingUnit cpu : getCpus()) { + final Inlet inlet = cpu.getInput(); + graph.disconnect(inlet); + } + + graph.disconnect(getMemory().getInput()); + + for (SimNetworkInterface ifx : getNetworkInterfaces()) { + ((NetworkAdapter) ifx).disconnect(); + } + + for (SimStorageInterface storage : getStorageInterfaces()) { + StorageDevice impl = (StorageDevice) storage; + graph.disconnect(impl.getRead()); + graph.disconnect(impl.getWrite()); + } + } + + @Override + public String toString() { + return "SimAbstractMachine.Context"; + } + } + + /** + * The [SimMemory] implementation for a machine. + */ + public static final class Memory implements SimMemory { + private final SimpleFlowSink sink; + private final List<MemoryUnit> models; + + public Memory(FlowGraph graph, List<MemoryUnit> models) { + long memorySize = 0; + for (MemoryUnit mem : models) { + memorySize += mem.getSize(); + } + + this.sink = new SimpleFlowSink(graph, (float) memorySize); + this.models = models; + } + + @Override + public double getCapacity() { + return sink.getCapacity(); + } + + @Override + public List<MemoryUnit> getModels() { + return models; + } + + @Override + public Inlet getInput() { + return sink.getInput(); + } + + @Override + public String toString() { + return "SimAbstractMachine.Memory"; + } + } + + /** + * A {@link SimNetworkAdapter} implementation for a machine. + */ + public static class NetworkAdapter extends SimNetworkAdapter implements SimNetworkInterface { + private final org.opendc.simulator.compute.model.NetworkAdapter model; + private final FlowTransformer tx; + private final FlowTransformer rx; + private final String name; + + /** + * Construct a {@link NetworkAdapter}. + */ + public NetworkAdapter(FlowGraph graph, org.opendc.simulator.compute.model.NetworkAdapter model, int index) { + this.model = model; + this.tx = new FlowTransformer(graph, FlowTransforms.noop()); + this.rx = new FlowTransformer(graph, FlowTransforms.noop()); + this.name = "eth" + index; + } + + @Override + public String getName() { + return name; + } + + @Override + public Inlet getTx() { + return tx.getInput(); + } + + @Override + public Outlet getRx() { + return rx.getOutput(); + } + + @Override + public double getBandwidth() { + return model.getBandwidth(); + } + + @Override + protected Outlet getOutlet() { + return tx.getOutput(); + } + + @Override + protected Inlet getInlet() { + return rx.getInput(); + } + + @Override + public String toString() { + return "SimAbstractMachine.NetworkAdapterImpl[name=" + name + ", bandwidth=" + model.getBandwidth() + + "Mbps]"; + } + } + + /** + * A {@link SimStorageInterface} implementation for a machine. + */ + public static class StorageDevice implements SimStorageInterface { + private final org.opendc.simulator.compute.model.StorageDevice model; + private final SimpleFlowSink read; + private final SimpleFlowSink write; + private final String name; + + /** + * Construct a {@link StorageDevice}. + */ + public StorageDevice(FlowGraph graph, org.opendc.simulator.compute.model.StorageDevice model, int index) { + this.model = model; + this.read = new SimpleFlowSink(graph, (float) model.getReadBandwidth()); + this.write = new SimpleFlowSink(graph, (float) model.getWriteBandwidth()); + this.name = "disk" + index; + } + + @Override + public String getName() { + return name; + } + + @Override + public Inlet getRead() { + return read.getInput(); + } + + @Override + public Inlet getWrite() { + return write.getInput(); + } + + @Override + public double getCapacity() { + return model.getCapacity(); + } + + @Override + public String toString() { + return "SimAbstractMachine.StorageDeviceImpl[name=" + name + ", capacity=" + model.getCapacity() + "MB]"; + } + } +} diff --git a/opendc-simulator/opendc-simulator-compute/src/main/java/org/opendc/simulator/compute/SimBareMetalMachine.java b/opendc-simulator/opendc-simulator-compute/src/main/java/org/opendc/simulator/compute/SimBareMetalMachine.java new file mode 100644 index 00000000..aa7502d6 --- /dev/null +++ b/opendc-simulator/opendc-simulator-compute/src/main/java/org/opendc/simulator/compute/SimBareMetalMachine.java @@ -0,0 +1,298 @@ +/* + * Copyright (c) 2022 AtLarge Research + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ + +package org.opendc.simulator.compute; + +import java.util.ArrayList; +import java.util.Collections; +import java.util.List; +import java.util.Map; +import org.opendc.simulator.compute.device.SimPeripheral; +import org.opendc.simulator.compute.model.MachineModel; +import org.opendc.simulator.compute.model.ProcessingUnit; +import org.opendc.simulator.compute.workload.SimWorkload; +import org.opendc.simulator.flow2.FlowGraph; +import org.opendc.simulator.flow2.InPort; +import org.opendc.simulator.flow2.Inlet; + +/** + * A simulated bare-metal machine that is able to run a single workload. + * + * <p> + * A {@link SimBareMetalMachine} is a stateful object, and you should be careful when operating this object concurrently. For + * example, the class expects only a single concurrent call to {@link #startWorkload(SimWorkload, Map)}. + */ +public final class SimBareMetalMachine extends SimAbstractMachine { + /** + * The {@link FlowGraph} in which the simulation takes places. + */ + private final FlowGraph graph; + + /** + * The {@link SimPsu} of this bare metal machine. + */ + private final SimPsu psu; + + /** + * The resources of this machine. + */ + private final List<Cpu> cpus; + + private final Memory memory; + private final List<NetworkAdapter> net; + private final List<StorageDevice> disk; + + /** + * Construct a {@link SimBareMetalMachine} instance. + * + * @param graph The {@link FlowGraph} to which the machine belongs. + * @param model The machine model to simulate. + * @param psuFactory The {@link SimPsuFactory} to construct the power supply of the machine. + */ + private SimBareMetalMachine(FlowGraph graph, MachineModel model, SimPsuFactory psuFactory) { + super(model); + + this.graph = graph; + this.psu = psuFactory.newPsu(this, graph); + + int cpuIndex = 0; + final ArrayList<Cpu> cpus = new ArrayList<>(); + this.cpus = cpus; + for (ProcessingUnit cpu : model.getCpus()) { + cpus.add(new Cpu(psu, cpu, cpuIndex++)); + } + + this.memory = new Memory(graph, model.getMemory()); + + int netIndex = 0; + final ArrayList<NetworkAdapter> net = new ArrayList<>(); + this.net = net; + for (org.opendc.simulator.compute.model.NetworkAdapter adapter : model.getNetwork()) { + net.add(new NetworkAdapter(graph, adapter, netIndex++)); + } + + int diskIndex = 0; + final ArrayList<StorageDevice> disk = new ArrayList<>(); + this.disk = disk; + for (org.opendc.simulator.compute.model.StorageDevice device : model.getStorage()) { + disk.add(new StorageDevice(graph, device, diskIndex++)); + } + } + + /** + * Create a {@link SimBareMetalMachine} instance. + * + * @param graph The {@link FlowGraph} to which the machine belongs. + * @param model The machine model to simulate. + * @param psuFactory The {@link SimPsuFactory} to construct the power supply of the machine. + */ + public static SimBareMetalMachine create(FlowGraph graph, MachineModel model, SimPsuFactory psuFactory) { + return new SimBareMetalMachine(graph, model, psuFactory); + } + + /** + * Create a {@link SimBareMetalMachine} instance with a no-op PSU. + * + * @param graph The {@link FlowGraph} to which the machine belongs. + * @param model The machine model to simulate. + */ + public static SimBareMetalMachine create(FlowGraph graph, MachineModel model) { + return new SimBareMetalMachine(graph, model, SimPsuFactories.noop()); + } + + /** + * Return the {@link SimPsu} belonging to this bare metal machine. + */ + public SimPsu getPsu() { + return psu; + } + + /** + * Return the list of peripherals attached to this bare metal machine. + */ + @Override + public List<? extends SimPeripheral> getPeripherals() { + return Collections.unmodifiableList(net); + } + + /** + * Return the CPU capacity of the machine in MHz. + */ + public double getCpuCapacity() { + final Context context = (Context) getActiveContext(); + + if (context == null) { + return 0.0; + } + + float capacity = 0.f; + + for (SimProcessingUnit cpu : context.cpus) { + capacity += cpu.getFrequency(); + } + + return capacity; + } + + /** + * The CPU demand of the machine in MHz. + */ + public double getCpuDemand() { + final Context context = (Context) getActiveContext(); + + if (context == null) { + return 0.0; + } + + float demand = 0.f; + + for (SimProcessingUnit cpu : context.cpus) { + demand += cpu.getDemand(); + } + + return demand; + } + + /** + * The CPU usage of the machine in MHz. + */ + public double getCpuUsage() { + final Context context = (Context) getActiveContext(); + + if (context == null) { + return 0.0; + } + + float rate = 0.f; + + for (SimProcessingUnit cpu : context.cpus) { + rate += cpu.getSpeed(); + } + + return rate; + } + + @Override + protected SimAbstractMachine.Context createContext(SimWorkload workload, Map<String, Object> meta) { + return new Context(this, workload, meta); + } + + /** + * The execution context for a {@link SimBareMetalMachine}. + */ + private static final class Context extends SimAbstractMachine.Context { + private final FlowGraph graph; + private final List<Cpu> cpus; + private final Memory memory; + private final List<NetworkAdapter> net; + private final List<StorageDevice> disk; + + private Context(SimBareMetalMachine machine, SimWorkload workload, Map<String, Object> meta) { + super(machine, workload, meta); + + this.graph = machine.graph; + this.cpus = machine.cpus; + this.memory = machine.memory; + this.net = machine.net; + this.disk = machine.disk; + } + + @Override + public FlowGraph getGraph() { + return graph; + } + + @Override + public List<? extends SimProcessingUnit> getCpus() { + return cpus; + } + + @Override + public SimMemory getMemory() { + return memory; + } + + @Override + public List<? extends SimNetworkInterface> getNetworkInterfaces() { + return net; + } + + @Override + public List<? extends SimStorageInterface> getStorageInterfaces() { + return disk; + } + } + + /** + * A {@link SimProcessingUnit} of a bare-metal machine. + */ + private static final class Cpu implements SimProcessingUnit { + private final SimPsu psu; + private final ProcessingUnit model; + private final InPort port; + + private Cpu(SimPsu psu, ProcessingUnit model, int id) { + this.psu = psu; + this.model = model; + this.port = psu.getCpuPower(id, model); + + this.port.pull((float) model.getFrequency()); + } + + @Override + public double getFrequency() { + return port.getCapacity(); + } + + @Override + public void setFrequency(double frequency) { + // Clamp the capacity of the CPU between [0.0, maxFreq] + frequency = Math.max(0, Math.min(model.getFrequency(), frequency)); + psu.setCpuFrequency(port, frequency); + } + + @Override + public double getDemand() { + return port.getDemand(); + } + + @Override + public double getSpeed() { + return port.getRate(); + } + + @Override + public ProcessingUnit getModel() { + return model; + } + + @Override + public Inlet getInput() { + return port; + } + + @Override + public String toString() { + return "SimBareMetalMachine.Cpu[model=" + model + "]"; + } + } +} diff --git a/opendc-simulator/opendc-simulator-compute/src/main/kotlin/org/opendc/simulator/compute/SimMachine.kt b/opendc-simulator/opendc-simulator-compute/src/main/java/org/opendc/simulator/compute/SimMachine.java index 94581e89..59599875 100644 --- a/opendc-simulator/opendc-simulator-compute/src/main/kotlin/org/opendc/simulator/compute/SimMachine.kt +++ b/opendc-simulator/opendc-simulator-compute/src/main/java/org/opendc/simulator/compute/SimMachine.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2020 AtLarge Research + * Copyright (c) 2022 AtLarge Research * * Permission is hereby granted, free of charge, to any person obtaining a copy * of this software and associated documentation files (the "Software"), to deal @@ -20,40 +20,40 @@ * SOFTWARE. */ -package org.opendc.simulator.compute +package org.opendc.simulator.compute; -import org.opendc.simulator.compute.device.SimPeripheral -import org.opendc.simulator.compute.model.MachineModel -import org.opendc.simulator.compute.workload.SimWorkload +import java.util.List; +import java.util.Map; +import org.opendc.simulator.compute.device.SimPeripheral; +import org.opendc.simulator.compute.model.MachineModel; +import org.opendc.simulator.compute.workload.SimWorkload; /** - * A generic machine that is able to run a [SimWorkload]. + * A generic machine that is able to execute {@link SimWorkload} objects. */ public interface SimMachine { /** - * The model of the machine containing its specifications. + * Return the model of the machine containing its specifications. */ - public val model: MachineModel + MachineModel getModel(); /** - * The peripherals attached to the machine. + * Return the peripherals attached to the machine. */ - public val peripherals: List<SimPeripheral> + List<? extends SimPeripheral> getPeripherals(); /** - * Start the specified [SimWorkload] on this machine. + * Start the specified {@link SimWorkload} on this machine. * * @param workload The workload to start on the machine. * @param meta The metadata to pass to the workload. - * @return A [SimMachineContext] that represents the execution context for the workload. + * @return A {@link SimMachineContext} that represents the execution context for the workload. * @throws IllegalStateException if a workload is already active on the machine or if the machine is closed. */ - public fun startWorkload(workload: SimWorkload, meta: Map<String, Any> = emptyMap()): SimMachineContext + SimMachineContext startWorkload(SimWorkload workload, Map<String, Object> meta); /** - * Cancel the workload that is currently running on this machine. - * - * If no workload is active, this operation is a no-op. + * Cancel the active workload on this machine (if any). */ - public fun cancel() + void cancel(); } diff --git a/opendc-simulator/opendc-simulator-compute/src/main/java/org/opendc/simulator/compute/SimMachineContext.java b/opendc-simulator/opendc-simulator-compute/src/main/java/org/opendc/simulator/compute/SimMachineContext.java new file mode 100644 index 00000000..f6a3bd38 --- /dev/null +++ b/opendc-simulator/opendc-simulator-compute/src/main/java/org/opendc/simulator/compute/SimMachineContext.java @@ -0,0 +1,74 @@ +/* + * Copyright (c) 2022 AtLarge Research + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ + +package org.opendc.simulator.compute; + +import java.util.List; +import java.util.Map; +import org.opendc.simulator.compute.workload.SimWorkload; +import org.opendc.simulator.flow2.FlowGraph; + +/** + * A simulated execution context in which a bootable image runs. + * + * <p> + * This interface represents the interface between the running image (e.g. operating system) and the physical + * or virtual firmware on which the image runs. + */ +public interface SimMachineContext { + /** + * Return the {@link FlowGraph} in which the workload executes. + */ + FlowGraph getGraph(); + + /** + * Return the metadata associated with the context. + * <p> + * Users can pass this metadata to the workload via {@link SimMachine#startWorkload(SimWorkload, Map)}. + */ + Map<String, Object> getMeta(); + + /** + * Return the CPUs available on the machine. + */ + List<? extends SimProcessingUnit> getCpus(); + + /** + * Return the memory interface of the machine. + */ + SimMemory getMemory(); + + /** + * Return the network interfaces available to the workload. + */ + List<? extends SimNetworkInterface> getNetworkInterfaces(); + + /** + * Return the storage devices available to the workload. + */ + List<? extends SimStorageInterface> getStorageInterfaces(); + + /** + * Shutdown the workload. + */ + void shutdown(); +} diff --git a/opendc-simulator/opendc-simulator-compute/src/main/kotlin/org/opendc/simulator/compute/SimMemory.kt b/opendc-simulator/opendc-simulator-compute/src/main/java/org/opendc/simulator/compute/SimMemory.java index b1aef495..4fcc64ab 100644 --- a/opendc-simulator/opendc-simulator-compute/src/main/kotlin/org/opendc/simulator/compute/SimMemory.kt +++ b/opendc-simulator/opendc-simulator-compute/src/main/java/org/opendc/simulator/compute/SimMemory.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2021 AtLarge Research + * Copyright (c) 2022 AtLarge Research * * Permission is hereby granted, free of charge, to any person obtaining a copy * of this software and associated documentation files (the "Software"), to deal @@ -20,17 +20,23 @@ * SOFTWARE. */ -package org.opendc.simulator.compute +package org.opendc.simulator.compute; -import org.opendc.simulator.compute.model.MemoryUnit -import org.opendc.simulator.flow.FlowConsumer +import java.util.List; +import org.opendc.simulator.compute.model.MemoryUnit; +import org.opendc.simulator.flow2.sink.FlowSink; /** * An interface to control the memory usage of simulated workloads. */ -public interface SimMemory : FlowConsumer { +public interface SimMemory extends FlowSink { /** - * The models representing the static information of the memory units supporting this interface. + * Return the total capacity of the memory (in MBs). */ - public val models: List<MemoryUnit> + double getCapacity(); + + /** + * Return the models representing the static information of the memory units supporting this interface. + */ + List<MemoryUnit> getModels(); } diff --git a/opendc-simulator/opendc-simulator-compute/src/main/kotlin/org/opendc/simulator/compute/SimNetworkInterface.kt b/opendc-simulator/opendc-simulator-compute/src/main/java/org/opendc/simulator/compute/SimNetworkInterface.java index 660b2871..4b623e59 100644 --- a/opendc-simulator/opendc-simulator-compute/src/main/kotlin/org/opendc/simulator/compute/SimNetworkInterface.kt +++ b/opendc-simulator/opendc-simulator-compute/src/main/java/org/opendc/simulator/compute/SimNetworkInterface.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2021 AtLarge Research + * Copyright (c) 2022 AtLarge Research * * Permission is hereby granted, free of charge, to any person obtaining a copy * of this software and associated documentation files (the "Software"), to deal @@ -20,32 +20,32 @@ * SOFTWARE. */ -package org.opendc.simulator.compute +package org.opendc.simulator.compute; -import org.opendc.simulator.flow.FlowConsumer -import org.opendc.simulator.flow.FlowSource +import org.opendc.simulator.flow2.Inlet; +import org.opendc.simulator.flow2.Outlet; /** * A firmware interface to a network adapter. */ public interface SimNetworkInterface { /** - * The name of the network interface. + * Return the name of the network interface. */ - public val name: String + String getName(); /** - * The unidirectional bandwidth of the network interface in Mbps. + * Return the unidirectional bandwidth of the network interface in Mbps. */ - public val bandwidth: Double + double getBandwidth(); /** - * The resource provider for the transmit channel of the network interface. + * Return the inlet for the "transmit" channel of the network interface. */ - public val tx: FlowConsumer + Inlet getTx(); /** - * The resource consumer for the receive channel of the network interface. + * Return the outlet for the "receive" channel of the network interface. */ - public val rx: FlowSource + Outlet getRx(); } diff --git a/opendc-simulator/opendc-simulator-flow/src/main/kotlin/org/opendc/simulator/flow/FlowConnection.kt b/opendc-simulator/opendc-simulator-compute/src/main/java/org/opendc/simulator/compute/SimProcessingUnit.java index 8ff0bc76..3dbd3656 100644 --- a/opendc-simulator/opendc-simulator-flow/src/main/kotlin/org/opendc/simulator/flow/FlowConnection.kt +++ b/opendc-simulator/opendc-simulator-compute/src/main/java/org/opendc/simulator/compute/SimProcessingUnit.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2021 AtLarge Research + * Copyright (c) 2022 AtLarge Research * * Permission is hereby granted, free of charge, to any person obtaining a copy * of this software and associated documentation files (the "Software"), to deal @@ -20,53 +20,43 @@ * SOFTWARE. */ -package org.opendc.simulator.flow +package org.opendc.simulator.compute; + +import org.opendc.simulator.compute.model.ProcessingUnit; +import org.opendc.simulator.flow2.sink.FlowSink; /** - * An active connection between a [FlowSource] and [FlowConsumer]. + * A simulated processing unit. */ -public interface FlowConnection : AutoCloseable { - /** - * The capacity of the connection. - */ - public val capacity: Double - - /** - * The flow rate over the connection. - */ - public val rate: Double - +public interface SimProcessingUnit extends FlowSink { /** - * The flow demand of the source. + * Return the base clock frequency of the processing unit (in MHz). */ - public val demand: Double + double getFrequency(); /** - * A flag to control whether [FlowSource.onConverge] should be invoked for this source. - */ - public var shouldSourceConverge: Boolean - - /** - * Pull the source. + * Adjust the base clock frequency of the processing unit. + * + * <p> + * The CPU may or may not round the new frequency to one of its pre-defined frequency steps. + * + * @param frequency The new frequency to set the clock of the processing unit to. + * @throws UnsupportedOperationException if the base clock cannot be adjusted. */ - public fun pull() + void setFrequency(double frequency); /** - * Pull the source. - * - * @param now The timestamp at which the connection is pulled. + * The demand on the processing unit. */ - public fun pull(now: Long) + double getDemand(); /** - * Push the given flow [rate] over this connection. - * - * @param rate The rate of the flow to push. + * The speed of the processing unit. */ - public fun push(rate: Double) + double getSpeed(); /** - * Disconnect the consumer from its source. + * The model representing the static properties of the processing unit. */ - public override fun close() + ProcessingUnit getModel(); } diff --git a/opendc-simulator/opendc-simulator-compute/src/main/java/org/opendc/simulator/compute/SimPsu.java b/opendc-simulator/opendc-simulator-compute/src/main/java/org/opendc/simulator/compute/SimPsu.java new file mode 100644 index 00000000..7f1f97a0 --- /dev/null +++ b/opendc-simulator/opendc-simulator-compute/src/main/java/org/opendc/simulator/compute/SimPsu.java @@ -0,0 +1,71 @@ +/* + * Copyright (c) 2022 AtLarge Research + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ + +package org.opendc.simulator.compute; + +import org.opendc.simulator.compute.model.ProcessingUnit; +import org.opendc.simulator.flow2.InPort; +import org.opendc.simulator.power.SimPowerInlet; + +/** + * A power supply unit in a {@link SimBareMetalMachine}. + * + * <p> + * This class manages the computation of power usage for a {@link SimBareMetalMachine} based on the resource usage. + */ +public abstract class SimPsu extends SimPowerInlet { + /** + * Return the power demand of the machine (in W) measured in the PSU. + * <p> + * This method provides access to the power consumption of the machine before PSU losses are applied. + */ + public abstract double getPowerDemand(); + + /** + * Return the instantaneous power usage of the machine (in W) measured at the inlet of the power supply. + */ + public abstract double getPowerUsage(); + + /** + * Return the cumulated energy usage of the machine (in J) measured at the inlet of the powers supply. + */ + public abstract double getEnergyUsage(); + + /** + * Return an {@link InPort} that converts processing demand (in MHz) into energy demand (J) for the specified CPU + * <code>model</code>. + * + * @param id The unique identifier of the CPU for this machine. + * @param model The details of the processing unit. + */ + abstract InPort getCpuPower(int id, ProcessingUnit model); + + /** + * This method is invoked when the CPU frequency is changed for the specified <code>port</code>. + * + * @param port The {@link InPort} for which the capacity is changed. + * @param capacity The capacity to change to. + */ + void setCpuFrequency(InPort port, double capacity) { + port.pull((float) capacity); + } +} diff --git a/opendc-simulator/opendc-simulator-compute/src/main/java/org/opendc/simulator/compute/SimPsuFactories.java b/opendc-simulator/opendc-simulator-compute/src/main/java/org/opendc/simulator/compute/SimPsuFactories.java new file mode 100644 index 00000000..52d04052 --- /dev/null +++ b/opendc-simulator/opendc-simulator-compute/src/main/java/org/opendc/simulator/compute/SimPsuFactories.java @@ -0,0 +1,214 @@ +/* + * Copyright (c) 2022 AtLarge Research + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ + +package org.opendc.simulator.compute; + +import java.time.Clock; +import org.jetbrains.annotations.NotNull; +import org.opendc.simulator.compute.model.ProcessingUnit; +import org.opendc.simulator.compute.power.CpuPowerModel; +import org.opendc.simulator.flow2.FlowGraph; +import org.opendc.simulator.flow2.FlowStage; +import org.opendc.simulator.flow2.FlowStageLogic; +import org.opendc.simulator.flow2.InHandler; +import org.opendc.simulator.flow2.InPort; +import org.opendc.simulator.flow2.OutPort; +import org.opendc.simulator.flow2.Outlet; + +/** + * A collection {@link SimPsu} implementations. + */ +public class SimPsuFactories { + private SimPsuFactories() {} + + /** + * Return a {@link SimPsuFactory} of {@link SimPsu} implementations that do not measure any power consumption. + * + * <p> + * This implementation has the lowest performance impact and users are advised to use this factory if they do not + * consider power consumption in their experiments. + */ + public static SimPsuFactory noop() { + return NoopPsu.FACTORY; + } + + /** + * Return a {@link SimPsuFactory} of {@link SimPsu} implementations that use a {@link CpuPowerModel} to estimate the + * power consumption of a machine based on its CPU utilization. + * + * @param model The power model to estimate the power consumption based on the CPU usage. + */ + public static SimPsuFactory simple(CpuPowerModel model) { + return (machine, graph) -> new SimplePsu(graph, model); + } + + /** + * A {@link SimPsu} implementation that does not attempt to measure power consumption. + */ + private static final class NoopPsu extends SimPsu implements FlowStageLogic { + private static final SimPsuFactory FACTORY = (machine, graph) -> new NoopPsu(graph); + + private final FlowStage stage; + private final OutPort out; + + NoopPsu(FlowGraph graph) { + stage = graph.newStage(this); + out = stage.getOutlet("out"); + out.setMask(true); + } + + @Override + public double getPowerDemand() { + return 0; + } + + @Override + public double getPowerUsage() { + return 0; + } + + @Override + public double getEnergyUsage() { + return 0; + } + + @Override + InPort getCpuPower(int id, ProcessingUnit model) { + final InPort port = stage.getInlet("cpu" + id); + port.setMask(true); + return port; + } + + @Override + public long onUpdate(FlowStage ctx, long now) { + return Long.MAX_VALUE; + } + + @NotNull + @Override + public Outlet getFlowOutlet() { + return out; + } + } + + /** + * A {@link SimPsu} implementation that estimates the power consumption based on CPU usage. + */ + private static final class SimplePsu extends SimPsu implements FlowStageLogic { + private final FlowStage stage; + private final OutPort out; + private final CpuPowerModel model; + private final Clock clock; + + private double targetFreq; + private double totalUsage; + private long lastUpdate; + + private double powerUsage; + private double energyUsage; + + private final InHandler handler = new InHandler() { + @Override + public void onPush(InPort port, float demand) { + totalUsage += -port.getDemand() + demand; + } + + @Override + public void onUpstreamFinish(InPort port, Throwable cause) { + totalUsage -= port.getDemand(); + } + }; + + SimplePsu(FlowGraph graph, CpuPowerModel model) { + this.stage = graph.newStage(this); + this.model = model; + this.clock = graph.getEngine().getClock(); + this.out = stage.getOutlet("out"); + this.out.setMask(true); + + lastUpdate = graph.getEngine().getClock().millis(); + } + + @Override + public double getPowerDemand() { + return totalUsage; + } + + @Override + public double getPowerUsage() { + return powerUsage; + } + + @Override + public double getEnergyUsage() { + updateEnergyUsage(clock.millis()); + return energyUsage; + } + + @Override + InPort getCpuPower(int id, ProcessingUnit model) { + targetFreq += model.getFrequency(); + + final InPort port = stage.getInlet("cpu" + id); + port.setHandler(handler); + return port; + } + + @Override + void setCpuFrequency(InPort port, double capacity) { + targetFreq += -port.getCapacity() + capacity; + + super.setCpuFrequency(port, capacity); + } + + @Override + public long onUpdate(FlowStage ctx, long now) { + updateEnergyUsage(now); + + double usage = model.computePower(totalUsage / targetFreq); + out.push((float) usage); + powerUsage = usage; + + return Long.MAX_VALUE; + } + + @NotNull + @Override + public Outlet getFlowOutlet() { + return out; + } + + /** + * Calculate the energy usage up until <code>now</code>. + */ + private void updateEnergyUsage(long now) { + long lastUpdate = this.lastUpdate; + this.lastUpdate = now; + + long duration = now - lastUpdate; + if (duration > 0) { + // Compute the energy usage of the machine + energyUsage += powerUsage * duration * 0.001; + } + } + } +} diff --git a/opendc-simulator/opendc-simulator-flow/src/main/kotlin/org/opendc/simulator/flow/FlowConvergenceListener.kt b/opendc-simulator/opendc-simulator-compute/src/main/java/org/opendc/simulator/compute/SimPsuFactory.java index 62cb10d1..872e7016 100644 --- a/opendc-simulator/opendc-simulator-flow/src/main/kotlin/org/opendc/simulator/flow/FlowConvergenceListener.kt +++ b/opendc-simulator/opendc-simulator-compute/src/main/java/org/opendc/simulator/compute/SimPsuFactory.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2021 AtLarge Research + * Copyright (c) 2022 AtLarge Research * * Permission is hereby granted, free of charge, to any person obtaining a copy * of this software and associated documentation files (the "Software"), to deal @@ -20,16 +20,19 @@ * SOFTWARE. */ -package org.opendc.simulator.flow +package org.opendc.simulator.compute; + +import org.opendc.simulator.flow2.FlowGraph; /** - * A listener interface for when a flow stage has converged into a steady-state. + * A factory interface for {@link SimPsu} implementations. */ -public interface FlowConvergenceListener { +public interface SimPsuFactory { /** - * This method is invoked when the system has converged to a steady-state. + * Construct a new {@link SimPsu} for the specified <code>machine</code>. * - * @param now The timestamp at which the system converged. + * @param machine The machine to construct the power supply for. + * @param graph The {@link FlowGraph} used for the simulation. */ - public fun onConverge(now: Long) {} + SimPsu newPsu(SimMachine machine, FlowGraph graph); } diff --git a/opendc-simulator/opendc-simulator-compute/src/main/kotlin/org/opendc/simulator/compute/SimStorageInterface.kt b/opendc-simulator/opendc-simulator-compute/src/main/java/org/opendc/simulator/compute/SimStorageInterface.java index 3d648671..341122dc 100644 --- a/opendc-simulator/opendc-simulator-compute/src/main/kotlin/org/opendc/simulator/compute/SimStorageInterface.kt +++ b/opendc-simulator/opendc-simulator-compute/src/main/java/org/opendc/simulator/compute/SimStorageInterface.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2021 AtLarge Research + * Copyright (c) 2022 AtLarge Research * * Permission is hereby granted, free of charge, to any person obtaining a copy * of this software and associated documentation files (the "Software"), to deal @@ -20,31 +20,31 @@ * SOFTWARE. */ -package org.opendc.simulator.compute +package org.opendc.simulator.compute; -import org.opendc.simulator.flow.FlowConsumer +import org.opendc.simulator.flow2.Inlet; /** * A firmware interface to a storage device. */ public interface SimStorageInterface { /** - * The name of the storage device. + * Return the name of the network interface. */ - public val name: String + String getName(); /** - * The capacity of the storage device in MBs. + * Return the capacity of the storage device in MBs. */ - public val capacity: Double + double getCapacity(); /** - * The resource provider for the read operations of the storage device. + * Return the inlet for the read operations of the storage device. */ - public val read: FlowConsumer + Inlet getRead(); /** - * The resource consumer for the write operation of the storage device. + * Return the inlet for the write operation of the storage device. */ - public val write: FlowConsumer + Inlet getWrite(); } diff --git a/opendc-simulator/opendc-simulator-compute/src/main/kotlin/org/opendc/simulator/compute/device/SimNetworkAdapter.kt b/opendc-simulator/opendc-simulator-compute/src/main/java/org/opendc/simulator/compute/device/SimNetworkAdapter.java index dfb4ecf3..1c16ceff 100644 --- a/opendc-simulator/opendc-simulator-compute/src/main/kotlin/org/opendc/simulator/compute/device/SimNetworkAdapter.kt +++ b/opendc-simulator/opendc-simulator-compute/src/main/java/org/opendc/simulator/compute/device/SimNetworkAdapter.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2021 AtLarge Research + * Copyright (c) 2022 AtLarge Research * * Permission is hereby granted, free of charge, to any person obtaining a copy * of this software and associated documentation files (the "Software"), to deal @@ -20,17 +20,17 @@ * SOFTWARE. */ -package org.opendc.simulator.compute.device +package org.opendc.simulator.compute.device; -import org.opendc.simulator.compute.SimMachine -import org.opendc.simulator.network.SimNetworkPort +import org.opendc.simulator.compute.SimMachine; +import org.opendc.simulator.network.SimNetworkPort; /** - * A simulated network interface card (NIC or network adapter) that can be attached to a [SimMachine]. + * A simulated network interface card (NIC or network adapter) that can be attached to a {@link SimMachine}. */ -public abstract class SimNetworkAdapter : SimNetworkPort(), SimPeripheral { +public abstract class SimNetworkAdapter extends SimNetworkPort implements SimPeripheral { /** - * The unidirectional bandwidth of the network adapter in Mbps. + * Return the unidirectional bandwidth of the network adapter (in Mbps). */ - public abstract val bandwidth: Double + public abstract double getBandwidth(); } diff --git a/opendc-simulator/opendc-simulator-compute/src/main/kotlin/org/opendc/simulator/compute/device/SimPeripheral.kt b/opendc-simulator/opendc-simulator-compute/src/main/java/org/opendc/simulator/compute/device/SimPeripheral.java index 268271be..40bd268b 100644 --- a/opendc-simulator/opendc-simulator-compute/src/main/kotlin/org/opendc/simulator/compute/device/SimPeripheral.kt +++ b/opendc-simulator/opendc-simulator-compute/src/main/java/org/opendc/simulator/compute/device/SimPeripheral.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2021 AtLarge Research + * Copyright (c) 2022 AtLarge Research * * Permission is hereby granted, free of charge, to any person obtaining a copy * of this software and associated documentation files (the "Software"), to deal @@ -20,14 +20,14 @@ * SOFTWARE. */ -package org.opendc.simulator.compute.device +package org.opendc.simulator.compute.device; -import org.opendc.simulator.compute.SimMachine +import org.opendc.simulator.compute.SimMachine; /** - * A component that can be attached to a [SimMachine]. - * + * A component that can be attached to a {@link SimMachine}. + * <p> * This interface represents the physical view of the peripheral and should be used to configure the physical properties * of the peripheral. */ -public interface SimPeripheral +public interface SimPeripheral {} diff --git a/opendc-simulator/opendc-simulator-compute/src/main/java/org/opendc/simulator/compute/kernel/SimHypervisor.java b/opendc-simulator/opendc-simulator-compute/src/main/java/org/opendc/simulator/compute/kernel/SimHypervisor.java new file mode 100644 index 00000000..6e295837 --- /dev/null +++ b/opendc-simulator/opendc-simulator-compute/src/main/java/org/opendc/simulator/compute/kernel/SimHypervisor.java @@ -0,0 +1,911 @@ +/* + * Copyright (c) 2022 AtLarge Research + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ + +package org.opendc.simulator.compute.kernel; + +import java.time.Clock; +import java.util.ArrayList; +import java.util.Collections; +import java.util.List; +import java.util.Map; +import java.util.SplittableRandom; +import java.util.stream.Collectors; +import org.opendc.simulator.compute.SimAbstractMachine; +import org.opendc.simulator.compute.SimMachine; +import org.opendc.simulator.compute.SimMachineContext; +import org.opendc.simulator.compute.SimMemory; +import org.opendc.simulator.compute.SimNetworkInterface; +import org.opendc.simulator.compute.SimProcessingUnit; +import org.opendc.simulator.compute.SimStorageInterface; +import org.opendc.simulator.compute.device.SimPeripheral; +import org.opendc.simulator.compute.kernel.cpufreq.ScalingGovernor; +import org.opendc.simulator.compute.kernel.cpufreq.ScalingGovernorFactory; +import org.opendc.simulator.compute.kernel.cpufreq.ScalingPolicy; +import org.opendc.simulator.compute.kernel.interference.VmInterferenceDomain; +import org.opendc.simulator.compute.kernel.interference.VmInterferenceMember; +import org.opendc.simulator.compute.kernel.interference.VmInterferenceProfile; +import org.opendc.simulator.compute.model.MachineModel; +import org.opendc.simulator.compute.model.ProcessingUnit; +import org.opendc.simulator.compute.workload.SimWorkload; +import org.opendc.simulator.flow2.FlowGraph; +import org.opendc.simulator.flow2.FlowStage; +import org.opendc.simulator.flow2.FlowStageLogic; +import org.opendc.simulator.flow2.InHandler; +import org.opendc.simulator.flow2.InPort; +import org.opendc.simulator.flow2.Inlet; +import org.opendc.simulator.flow2.OutHandler; +import org.opendc.simulator.flow2.OutPort; +import org.opendc.simulator.flow2.mux.FlowMultiplexer; +import org.opendc.simulator.flow2.mux.FlowMultiplexerFactory; + +/** + * A SimHypervisor facilitates the execution of multiple concurrent {@link SimWorkload}s, while acting as a single + * workload to another {@link SimMachine}. + */ +public final class SimHypervisor implements SimWorkload { + private final FlowMultiplexerFactory muxFactory; + private final SplittableRandom random; + private final ScalingGovernorFactory scalingGovernorFactory; + private final VmInterferenceDomain interferenceDomain; + + private Context activeContext; + private final ArrayList<VirtualMachine> vms = new ArrayList<>(); + private final HvCounters counters = new HvCounters(); + + /** + * Construct a {@link SimHypervisor} instance. + * + * @param muxFactory The factory for the {@link FlowMultiplexer} to multiplex the workloads. + * @param random A randomness generator for the interference calculations. + * @param scalingGovernorFactory The factory for the scaling governor to use for scaling the CPU frequency. + * @param interferenceDomain The interference domain to which the hypervisor belongs. + */ + private SimHypervisor( + FlowMultiplexerFactory muxFactory, + SplittableRandom random, + ScalingGovernorFactory scalingGovernorFactory, + VmInterferenceDomain interferenceDomain) { + this.muxFactory = muxFactory; + this.random = random; + this.scalingGovernorFactory = scalingGovernorFactory; + this.interferenceDomain = interferenceDomain; + } + + /** + * Create a {@link SimHypervisor} instance. + * + * @param muxFactory The factory for the {@link FlowMultiplexer} to multiplex the workloads. + * @param random A randomness generator for the interference calculations. + * @param scalingGovernorFactory The factory for the scaling governor to use for scaling the CPU frequency. + * @param interferenceDomain The interference domain to which the hypervisor belongs. + */ + public static SimHypervisor create( + FlowMultiplexerFactory muxFactory, + SplittableRandom random, + ScalingGovernorFactory scalingGovernorFactory, + VmInterferenceDomain interferenceDomain) { + return new SimHypervisor(muxFactory, random, scalingGovernorFactory, interferenceDomain); + } + + /** + * Create a {@link SimHypervisor} instance with a default interference domain. + * + * @param muxFactory The factory for the {@link FlowMultiplexer} to multiplex the workloads. + * @param random A randomness generator for the interference calculations. + * @param scalingGovernorFactory The factory for the scaling governor to use for scaling the CPU frequency. + */ + public static SimHypervisor create( + FlowMultiplexerFactory muxFactory, SplittableRandom random, ScalingGovernorFactory scalingGovernorFactory) { + return create(muxFactory, random, scalingGovernorFactory, new VmInterferenceDomain()); + } + + /** + * Create a {@link SimHypervisor} instance with a default interference domain and scaling governor. + * + * @param muxFactory The factory for the {@link FlowMultiplexer} to multiplex the workloads. + * @param random A randomness generator for the interference calculations. + */ + public static SimHypervisor create(FlowMultiplexerFactory muxFactory, SplittableRandom random) { + return create(muxFactory, random, null); + } + + /** + * Return the performance counters of the hypervisor. + */ + public SimHypervisorCounters getCounters() { + return counters; + } + + /** + * Return the virtual machines running on this hypervisor. + */ + public List<? extends SimVirtualMachine> getVirtualMachines() { + return Collections.unmodifiableList(vms); + } + + /** + * Create a {@link SimVirtualMachine} instance on which users may run a [SimWorkload]. + * + * @param model The machine to create. + */ + public SimVirtualMachine newMachine(MachineModel model) { + if (!canFit(model)) { + throw new IllegalArgumentException("Machine does not fit"); + } + + VirtualMachine vm = new VirtualMachine(model); + vms.add(vm); + return vm; + } + + /** + * Remove the specified <code>machine</code> from the hypervisor. + * + * @param machine The machine to remove. + */ + public void removeMachine(SimVirtualMachine machine) { + if (vms.remove(machine)) { + // This cast must always succeed, since `_vms` only contains `VirtualMachine` types. + ((VirtualMachine) machine).close(); + } + } + + /** + * Return the CPU capacity of the hypervisor in MHz. + */ + public double getCpuCapacity() { + final Context context = activeContext; + + if (context == null) { + return 0.0; + } + + return context.previousCapacity; + } + + /** + * The CPU demand of the hypervisor in MHz. + */ + public double getCpuDemand() { + final Context context = activeContext; + + if (context == null) { + return 0.0; + } + + return context.previousDemand; + } + + /** + * The CPU usage of the hypervisor in MHz. + */ + public double getCpuUsage() { + final Context context = activeContext; + + if (context == null) { + return 0.0; + } + + return context.previousRate; + } + + /** + * Determine whether the specified machine characterized by <code>model</code> can fit on this hypervisor at this + * moment. + */ + public boolean canFit(MachineModel model) { + final Context context = activeContext; + if (context == null) { + return false; + } + + final FlowMultiplexer multiplexer = context.multiplexer; + return (multiplexer.getMaxInputs() - multiplexer.getInputCount()) + >= model.getCpus().size(); + } + + @Override + public void onStart(SimMachineContext ctx) { + final Context context = new Context(ctx, muxFactory, scalingGovernorFactory, counters); + context.start(); + activeContext = context; + } + + @Override + public void onStop(SimMachineContext ctx) { + final Context context = activeContext; + if (context != null) { + activeContext = null; + context.stop(); + } + } + + /** + * The context which carries the state when the hypervisor is running on a machine. + */ + private static final class Context implements FlowStageLogic { + private final SimMachineContext ctx; + private final FlowMultiplexer multiplexer; + private final FlowStage stage; + private final List<ScalingGovernor> scalingGovernors; + private final Clock clock; + private final HvCounters counters; + + private long lastCounterUpdate; + private final double d; + private float previousDemand; + private float previousRate; + private float previousCapacity; + + private Context( + SimMachineContext ctx, + FlowMultiplexerFactory muxFactory, + ScalingGovernorFactory scalingGovernorFactory, + HvCounters counters) { + + this.ctx = ctx; + this.counters = counters; + + final FlowGraph graph = ctx.getGraph(); + this.multiplexer = muxFactory.newMultiplexer(graph); + this.stage = graph.newStage(this); + this.clock = graph.getEngine().getClock(); + + this.lastCounterUpdate = clock.millis(); + + if (scalingGovernorFactory != null) { + this.scalingGovernors = ctx.getCpus().stream() + .map(cpu -> scalingGovernorFactory.newGovernor(new ScalingPolicyImpl(cpu))) + .collect(Collectors.toList()); + } else { + this.scalingGovernors = Collections.emptyList(); + } + + float cpuCapacity = 0.f; + final List<? extends SimProcessingUnit> cpus = ctx.getCpus(); + for (SimProcessingUnit cpu : cpus) { + cpuCapacity += cpu.getFrequency(); + } + this.d = cpus.size() / cpuCapacity; + } + + /** + * Start the hypervisor on a new machine. + */ + void start() { + final FlowGraph graph = ctx.getGraph(); + final FlowMultiplexer multiplexer = this.multiplexer; + + for (SimProcessingUnit cpu : ctx.getCpus()) { + graph.connect(multiplexer.newOutput(), cpu.getInput()); + } + + for (ScalingGovernor governor : scalingGovernors) { + governor.onStart(); + } + } + + /** + * Stop the hypervisor. + */ + void stop() { + // Synchronize the counters before stopping the hypervisor. Otherwise, the last report is missed. + updateCounters(clock.millis()); + + stage.close(); + } + + /** + * Invalidate the {@link FlowStage} of the hypervisor. + */ + void invalidate() { + stage.invalidate(); + } + + /** + * Update the performance counters of the hypervisor. + * + * @param now The timestamp at which to update the counter. + */ + void updateCounters(long now) { + long lastUpdate = this.lastCounterUpdate; + this.lastCounterUpdate = now; + long delta = now - lastUpdate; + + if (delta > 0) { + final HvCounters counters = this.counters; + + float demand = previousDemand; + float rate = previousRate; + float capacity = previousCapacity; + + final double factor = this.d * delta; + + counters.cpuActiveTime += Math.round(rate * factor); + counters.cpuIdleTime += Math.round((capacity - rate) * factor); + counters.cpuStealTime += Math.round((demand - rate) * factor); + } + } + + /** + * Update the performance counters of the hypervisor. + */ + void updateCounters() { + updateCounters(clock.millis()); + } + + @Override + public long onUpdate(FlowStage ctx, long now) { + updateCounters(now); + + final FlowMultiplexer multiplexer = this.multiplexer; + final List<ScalingGovernor> scalingGovernors = this.scalingGovernors; + + float demand = multiplexer.getDemand(); + float rate = multiplexer.getRate(); + float capacity = multiplexer.getCapacity(); + + this.previousDemand = demand; + this.previousRate = rate; + this.previousCapacity = capacity; + + double load = rate / Math.min(1.0, capacity); + + if (!scalingGovernors.isEmpty()) { + for (ScalingGovernor governor : scalingGovernors) { + governor.onLimit(load); + } + } + + return Long.MAX_VALUE; + } + } + + /** + * A {@link ScalingPolicy} for a physical CPU of the hypervisor. + */ + private static final class ScalingPolicyImpl implements ScalingPolicy { + private final SimProcessingUnit cpu; + + private ScalingPolicyImpl(SimProcessingUnit cpu) { + this.cpu = cpu; + } + + @Override + public SimProcessingUnit getCpu() { + return cpu; + } + + @Override + public double getTarget() { + return cpu.getFrequency(); + } + + @Override + public void setTarget(double target) { + cpu.setFrequency(target); + } + + @Override + public double getMin() { + return 0; + } + + @Override + public double getMax() { + return cpu.getModel().getFrequency(); + } + } + + /** + * A virtual machine running on the hypervisor. + */ + private class VirtualMachine extends SimAbstractMachine implements SimVirtualMachine { + private boolean isClosed; + private final VmCounters counters = new VmCounters(this); + + private VirtualMachine(MachineModel model) { + super(model); + } + + @Override + public SimHypervisorCounters getCounters() { + return counters; + } + + @Override + public double getCpuDemand() { + final VmContext context = (VmContext) getActiveContext(); + + if (context == null) { + return 0.0; + } + + return context.previousDemand; + } + + @Override + public double getCpuUsage() { + final VmContext context = (VmContext) getActiveContext(); + + if (context == null) { + return 0.0; + } + + return context.usage; + } + + @Override + public double getCpuCapacity() { + final VmContext context = (VmContext) getActiveContext(); + + if (context == null) { + return 0.0; + } + + return context.previousCapacity; + } + + @Override + public List<? extends SimPeripheral> getPeripherals() { + return Collections.emptyList(); + } + + @Override + protected Context createContext(SimWorkload workload, Map<String, Object> meta) { + if (isClosed) { + throw new IllegalStateException("Virtual machine does not exist anymore"); + } + + final SimHypervisor.Context context = activeContext; + if (context == null) { + throw new IllegalStateException("Hypervisor is inactive"); + } + + return new VmContext( + context, this, random, interferenceDomain, counters, SimHypervisor.this.counters, workload, meta); + } + + @Override + public Context getActiveContext() { + return super.getActiveContext(); + } + + void close() { + if (isClosed) { + return; + } + + isClosed = true; + cancel(); + } + } + + /** + * A {@link SimAbstractMachine.Context} for a virtual machine instance. + */ + private static final class VmContext extends SimAbstractMachine.Context implements FlowStageLogic { + private final Context context; + private final SplittableRandom random; + private final VmCounters vmCounters; + private final HvCounters hvCounters; + private final VmInterferenceMember interferenceMember; + private final FlowStage stage; + private final FlowMultiplexer multiplexer; + private final Clock clock; + + private final List<VCpu> cpus; + private final SimAbstractMachine.Memory memory; + private final List<SimAbstractMachine.NetworkAdapter> net; + private final List<SimAbstractMachine.StorageDevice> disk; + + private final Inlet[] muxInlets; + private long lastUpdate; + private long lastCounterUpdate; + private final double d; + + private float demand; + private float usage; + private float capacity; + + private float previousDemand; + private float previousCapacity; + + private VmContext( + Context context, + VirtualMachine machine, + SplittableRandom random, + VmInterferenceDomain interferenceDomain, + VmCounters vmCounters, + HvCounters hvCounters, + SimWorkload workload, + Map<String, Object> meta) { + super(machine, workload, meta); + + this.context = context; + this.random = random; + this.vmCounters = vmCounters; + this.hvCounters = hvCounters; + this.clock = context.clock; + + final VmInterferenceProfile interferenceProfile = (VmInterferenceProfile) meta.get("interference-profile"); + VmInterferenceMember interferenceMember = null; + if (interferenceDomain != null && interferenceProfile != null) { + interferenceMember = interferenceDomain.join(interferenceProfile); + interferenceMember.activate(); + } + this.interferenceMember = interferenceMember; + + final FlowGraph graph = context.ctx.getGraph(); + final FlowStage stage = graph.newStage(this); + this.stage = stage; + this.lastUpdate = clock.millis(); + this.lastCounterUpdate = clock.millis(); + + final FlowMultiplexer multiplexer = context.multiplexer; + this.multiplexer = multiplexer; + + final MachineModel model = machine.getModel(); + final List<ProcessingUnit> cpuModels = model.getCpus(); + final Inlet[] muxInlets = new Inlet[cpuModels.size()]; + final ArrayList<VCpu> cpus = new ArrayList<>(); + + this.muxInlets = muxInlets; + this.cpus = cpus; + + float capacity = 0.f; + + for (int i = 0; i < cpuModels.size(); i++) { + final Inlet muxInlet = multiplexer.newInput(); + muxInlets[i] = muxInlet; + + final InPort input = stage.getInlet("cpu" + i); + final OutPort output = stage.getOutlet("mux" + i); + + final Handler handler = new Handler(this, input, output); + input.setHandler(handler); + output.setHandler(handler); + + final ProcessingUnit cpuModel = cpuModels.get(i); + capacity += cpuModel.getFrequency(); + + final VCpu cpu = new VCpu(cpuModel, input); + cpus.add(cpu); + + graph.connect(output, muxInlet); + } + this.d = cpuModels.size() / capacity; + + this.memory = new SimAbstractMachine.Memory(graph, model.getMemory()); + + int netIndex = 0; + final ArrayList<SimAbstractMachine.NetworkAdapter> net = new ArrayList<>(); + this.net = net; + for (org.opendc.simulator.compute.model.NetworkAdapter adapter : model.getNetwork()) { + net.add(new SimAbstractMachine.NetworkAdapter(graph, adapter, netIndex++)); + } + + int diskIndex = 0; + final ArrayList<SimAbstractMachine.StorageDevice> disk = new ArrayList<>(); + this.disk = disk; + for (org.opendc.simulator.compute.model.StorageDevice device : model.getStorage()) { + disk.add(new SimAbstractMachine.StorageDevice(graph, device, diskIndex++)); + } + } + + /** + * Update the performance counters of the virtual machine. + * + * @param now The timestamp at which to update the counter. + */ + void updateCounters(long now) { + long lastUpdate = this.lastCounterUpdate; + this.lastCounterUpdate = now; + long delta = now - lastUpdate; + + if (delta > 0) { + final VmCounters counters = this.vmCounters; + + float demand = this.previousDemand; + float rate = this.usage; + float capacity = this.previousCapacity; + + final double factor = this.d * delta; + final double active = rate * factor; + + counters.cpuActiveTime += Math.round(active); + counters.cpuIdleTime += Math.round((capacity - rate) * factor); + counters.cpuStealTime += Math.round((demand - rate) * factor); + } + } + + /** + * Update the performance counters of the virtual machine. + */ + void updateCounters() { + updateCounters(clock.millis()); + } + + @Override + public FlowGraph getGraph() { + return stage.getGraph(); + } + + @Override + public List<? extends SimProcessingUnit> getCpus() { + return cpus; + } + + @Override + public SimMemory getMemory() { + return memory; + } + + @Override + public List<? extends SimNetworkInterface> getNetworkInterfaces() { + return net; + } + + @Override + public List<? extends SimStorageInterface> getStorageInterfaces() { + return disk; + } + + @Override + public long onUpdate(FlowStage ctx, long now) { + float usage = 0.f; + for (Inlet inlet : muxInlets) { + usage += ((InPort) inlet).getRate(); + } + this.usage = usage; + this.previousDemand = demand; + this.previousCapacity = capacity; + + long lastUpdate = this.lastUpdate; + this.lastUpdate = now; + long delta = now - lastUpdate; + + if (delta > 0) { + final VmInterferenceMember interferenceMember = this.interferenceMember; + double penalty = 0.0; + + if (interferenceMember != null) { + final FlowMultiplexer multiplexer = this.multiplexer; + double load = multiplexer.getRate() / Math.min(1.0, multiplexer.getCapacity()); + penalty = 1 - interferenceMember.apply(random, load); + } + + final double factor = this.d * delta; + final long lostTime = Math.round(factor * usage * penalty); + + this.vmCounters.cpuLostTime += lostTime; + this.hvCounters.cpuLostTime += lostTime; + } + + // Invalidate the FlowStage of the hypervisor to update its counters (via onUpdate) + context.invalidate(); + + return Long.MAX_VALUE; + } + + @Override + protected void doCancel() { + super.doCancel(); + + // Synchronize the counters before stopping the hypervisor. Otherwise, the last report is missed. + updateCounters(clock.millis()); + + stage.close(); + + final FlowMultiplexer multiplexer = this.multiplexer; + for (Inlet muxInlet : muxInlets) { + multiplexer.releaseInput(muxInlet); + } + + final VmInterferenceMember interferenceMember = this.interferenceMember; + if (interferenceMember != null) { + interferenceMember.deactivate(); + } + } + } + + /** + * A {@link SimProcessingUnit} of a virtual machine. + */ + private static final class VCpu implements SimProcessingUnit { + private final ProcessingUnit model; + private final InPort input; + + private VCpu(ProcessingUnit model, InPort input) { + this.model = model; + this.input = input; + + input.pull((float) model.getFrequency()); + } + + @Override + public double getFrequency() { + return input.getCapacity(); + } + + @Override + public void setFrequency(double frequency) { + input.pull((float) frequency); + } + + @Override + public double getDemand() { + return input.getDemand(); + } + + @Override + public double getSpeed() { + return input.getRate(); + } + + @Override + public ProcessingUnit getModel() { + return model; + } + + @Override + public Inlet getInput() { + return input; + } + + @Override + public String toString() { + return "SimHypervisor.VCpu[model" + model + "]"; + } + } + + /** + * A handler for forwarding flow between an inlet and outlet. + */ + private static class Handler implements InHandler, OutHandler { + private final InPort input; + private final OutPort output; + private final VmContext context; + + private Handler(VmContext context, InPort input, OutPort output) { + this.context = context; + this.input = input; + this.output = output; + } + + @Override + public void onPush(InPort port, float demand) { + context.demand += -port.getDemand() + demand; + + output.push(demand); + } + + @Override + public void onUpstreamFinish(InPort port, Throwable cause) { + context.demand -= port.getDemand(); + + output.push(0.f); + } + + @Override + public float getRate(InPort port) { + return output.getRate(); + } + + @Override + public void onPull(OutPort port, float capacity) { + context.capacity += -port.getCapacity() + capacity; + + input.pull(capacity); + } + + @Override + public void onDownstreamFinish(OutPort port, Throwable cause) { + context.capacity -= port.getCapacity(); + + input.pull(0.f); + } + } + + /** + * Implementation of {@link SimHypervisorCounters} for the hypervisor. + */ + private class HvCounters implements SimHypervisorCounters { + private long cpuActiveTime; + private long cpuIdleTime; + private long cpuStealTime; + private long cpuLostTime; + + @Override + public long getCpuActiveTime() { + return cpuActiveTime; + } + + @Override + public long getCpuIdleTime() { + return cpuIdleTime; + } + + @Override + public long getCpuStealTime() { + return cpuStealTime; + } + + @Override + public long getCpuLostTime() { + return cpuLostTime; + } + + @Override + public void sync() { + final Context context = activeContext; + + if (context != null) { + context.updateCounters(); + } + } + } + + /** + * Implementation of {@link SimHypervisorCounters} for the virtual machine. + */ + private static class VmCounters implements SimHypervisorCounters { + private final VirtualMachine vm; + private long cpuActiveTime; + private long cpuIdleTime; + private long cpuStealTime; + private long cpuLostTime; + + private VmCounters(VirtualMachine vm) { + this.vm = vm; + } + + @Override + public long getCpuActiveTime() { + return cpuActiveTime; + } + + @Override + public long getCpuIdleTime() { + return cpuIdleTime; + } + + @Override + public long getCpuStealTime() { + return cpuStealTime; + } + + @Override + public long getCpuLostTime() { + return cpuLostTime; + } + + @Override + public void sync() { + final VmContext context = (VmContext) vm.getActiveContext(); + + if (context != null) { + context.updateCounters(); + } + } + } +} diff --git a/opendc-simulator/opendc-simulator-compute/src/main/kotlin/org/opendc/simulator/compute/kernel/SimHypervisorCounters.kt b/opendc-simulator/opendc-simulator-compute/src/main/java/org/opendc/simulator/compute/kernel/SimHypervisorCounters.java index 63fee507..fc77e9d6 100644 --- a/opendc-simulator/opendc-simulator-compute/src/main/kotlin/org/opendc/simulator/compute/kernel/SimHypervisorCounters.kt +++ b/opendc-simulator/opendc-simulator-compute/src/main/java/org/opendc/simulator/compute/kernel/SimHypervisorCounters.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2021 AtLarge Research + * Copyright (c) 2022 AtLarge Research * * Permission is hereby granted, free of charge, to any person obtaining a copy * of this software and associated documentation files (the "Software"), to deal @@ -20,34 +20,34 @@ * SOFTWARE. */ -package org.opendc.simulator.compute.kernel +package org.opendc.simulator.compute.kernel; /** - * Performance counters of a [SimHypervisor]. + * Performance counters of a {@link SimHypervisor}. */ public interface SimHypervisorCounters { /** - * The amount of time (in milliseconds) the CPUs of the hypervisor were actively running. + * Return the amount of time (in milliseconds) the CPUs of the hypervisor were actively running. */ - public val cpuActiveTime: Long + long getCpuActiveTime(); /** - * The amount of time (in milliseconds) the CPUs of the hypervisor were idle. + * Return the amount of time (in milliseconds) the CPUs of the hypervisor were idle. */ - public val cpuIdleTime: Long + long getCpuIdleTime(); /** - * The amount of CPU time (in milliseconds) that virtual machines were ready to run, but were not able to. + * Return the amount of CPU time (in milliseconds) that virtual machines were ready to run, but were not able to. */ - public val cpuStealTime: Long + long getCpuStealTime(); /** - * The amount of CPU time (in milliseconds) that was lost due to interference between virtual machines. + * Return the amount of CPU time (in milliseconds) that was lost due to interference between virtual machines. */ - public val cpuLostTime: Long + long getCpuLostTime(); /** - * Flush the counter values. + * Synchronize the counter values. */ - public fun flush() + void sync(); } diff --git a/opendc-simulator/opendc-simulator-compute/src/main/kotlin/org/opendc/simulator/compute/kernel/SimVirtualMachine.kt b/opendc-simulator/opendc-simulator-compute/src/main/java/org/opendc/simulator/compute/kernel/SimVirtualMachine.java index 36219ef2..fdf5e47f 100644 --- a/opendc-simulator/opendc-simulator-compute/src/main/kotlin/org/opendc/simulator/compute/kernel/SimVirtualMachine.kt +++ b/opendc-simulator/opendc-simulator-compute/src/main/java/org/opendc/simulator/compute/kernel/SimVirtualMachine.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2021 AtLarge Research + * Copyright (c) 2022 AtLarge Research * * Permission is hereby granted, free of charge, to any person obtaining a copy * of this software and associated documentation files (the "Software"), to deal @@ -20,31 +20,31 @@ * SOFTWARE. */ -package org.opendc.simulator.compute.kernel +package org.opendc.simulator.compute.kernel; -import org.opendc.simulator.compute.SimMachine +import org.opendc.simulator.compute.SimMachine; /** - * A virtual [SimMachine] running on top of another [SimMachine]. + * A virtual {@link SimMachine} running on top of another {@link SimMachine}. */ -public interface SimVirtualMachine : SimMachine { +public interface SimVirtualMachine extends SimMachine { /** - * The resource counters associated with the virtual machine. + * Return the performance counters associated with the virtual machine. */ - public val counters: SimHypervisorCounters + SimHypervisorCounters getCounters(); /** - * The CPU usage of the VM in MHz. + * Return the CPU usage of the VM in MHz. */ - public val cpuUsage: Double + double getCpuUsage(); /** - * The CPU usage of the VM in MHz. + * Return the CPU usage of the VM in MHz. */ - public val cpuDemand: Double + double getCpuDemand(); /** - * The CPU capacity of the VM in MHz. + * Return the CPU capacity of the VM in MHz. */ - public val cpuCapacity: Double + double getCpuCapacity(); } diff --git a/opendc-simulator/opendc-simulator-compute/src/main/kotlin/org/opendc/simulator/compute/kernel/cpufreq/ScalingGovernor.kt b/opendc-simulator/opendc-simulator-compute/src/main/java/org/opendc/simulator/compute/kernel/cpufreq/ScalingGovernor.java index d33827db..69a371e1 100644 --- a/opendc-simulator/opendc-simulator-compute/src/main/kotlin/org/opendc/simulator/compute/kernel/cpufreq/ScalingGovernor.kt +++ b/opendc-simulator/opendc-simulator-compute/src/main/java/org/opendc/simulator/compute/kernel/cpufreq/ScalingGovernor.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2021 AtLarge Research + * Copyright (c) 2022 AtLarge Research * * Permission is hereby granted, free of charge, to any person obtaining a copy * of this software and associated documentation files (the "Software"), to deal @@ -20,37 +20,27 @@ * SOFTWARE. */ -package org.opendc.simulator.compute.kernel.cpufreq +package org.opendc.simulator.compute.kernel.cpufreq; /** * A [ScalingGovernor] in the CPUFreq subsystem of OpenDC is responsible for scaling the frequency of simulated CPUs * independent of the particular implementation of the CPU. * + * <p> * Each of the scaling governors implements a single, possibly parametrized, performance scaling algorithm. * - * For more information, see the documentation of the Linux CPUFreq subsystem: - * https://www.kernel.org/doc/html/latest/admin-guide/pm/cpufreq.html + * @see <a href="https://www.kernel.org/doc/html/latest/admin-guide/pm/cpufreq.html">documentation of the Linux CPUFreq subsystem</a>. */ public interface ScalingGovernor { /** - * Create the scaling logic for the specified [policy] + * This method is invoked when the governor is started. */ - public fun createLogic(policy: ScalingPolicy): Logic + default void onStart() {} /** - * The logic of the scaling governor. + * This method is invoked when the governor should re-decide the frequency limits. + * + * @param load The load of the system. */ - public interface Logic { - /** - * This method is invoked when the governor is started. - */ - public fun onStart() {} - - /** - * This method is invoked when the governor should re-decide the frequency limits. - * - * @param load The load of the system. - */ - public fun onLimit(load: Double) {} - } + default void onLimit(double load) {} } diff --git a/opendc-simulator/opendc-simulator-compute/src/main/java/org/opendc/simulator/compute/kernel/cpufreq/ScalingGovernorFactory.java b/opendc-simulator/opendc-simulator-compute/src/main/java/org/opendc/simulator/compute/kernel/cpufreq/ScalingGovernorFactory.java new file mode 100644 index 00000000..97a49879 --- /dev/null +++ b/opendc-simulator/opendc-simulator-compute/src/main/java/org/opendc/simulator/compute/kernel/cpufreq/ScalingGovernorFactory.java @@ -0,0 +1,33 @@ +/* + * Copyright (c) 2022 AtLarge Research + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ + +package org.opendc.simulator.compute.kernel.cpufreq; + +/** + * Factory interface for a {@link ScalingGovernor}. + */ +public interface ScalingGovernorFactory { + /** + * Create the scaling logic for the specified {@link ScalingPolicy}. + */ + ScalingGovernor newGovernor(ScalingPolicy policy); +} diff --git a/opendc-simulator/opendc-simulator-compute/src/main/java/org/opendc/simulator/compute/kernel/cpufreq/ScalingGovernors.java b/opendc-simulator/opendc-simulator-compute/src/main/java/org/opendc/simulator/compute/kernel/cpufreq/ScalingGovernors.java new file mode 100644 index 00000000..2b10ae59 --- /dev/null +++ b/opendc-simulator/opendc-simulator-compute/src/main/java/org/opendc/simulator/compute/kernel/cpufreq/ScalingGovernors.java @@ -0,0 +1,190 @@ +/* + * Copyright (c) 2022 AtLarge Research + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ + +package org.opendc.simulator.compute.kernel.cpufreq; + +/** + * Collection of common {@link ScalingGovernor} implementations. + */ +public class ScalingGovernors { + private ScalingGovernors() {} + + /** + * Return a {@link ScalingGovernorFactory} for the <code>performance</code> scaling governor. + * + * <p> + * This governor causes the highest possible frequency to be requested from the CPUs. + */ + public static ScalingGovernorFactory performance() { + return PerformanceScalingGovernor.FACTORY; + } + + /** + * Return a {@link ScalingGovernorFactory} for the <code>powersave</code> scaling governor. + * + * <p> + * This governor causes the lowest possible frequency to be requested from the CPUs. + */ + public static ScalingGovernorFactory powerSave() { + return PowerSaveScalingGovernor.FACTORY; + } + + /** + * Return a {@link ScalingGovernorFactory} for the <code>conservative</code> scaling governor from the Linux kernel. + * + * @param threshold The threshold before scaling. + * @param stepSize The size of the frequency steps (use negative value for automatic). + */ + public static ScalingGovernorFactory conservative(double threshold, double stepSize) { + return (policy) -> new ConservativeScalingGovernor(policy, threshold, stepSize); + } + + /** + * Return a {@link ScalingGovernorFactory} for the <code>conservative</code> scaling governor from the Linux kernel. + * + * @param threshold The threshold before scaling. + */ + public static ScalingGovernorFactory conservative(double threshold) { + return conservative(threshold, -1.0); + } + + /** + * Return a {@link ScalingGovernorFactory} for the <code>ondemand</code> scaling governor from the Linux kernel. + * + * @param threshold The threshold before scaling. + */ + public static ScalingGovernorFactory ondemand(double threshold) { + return (policy) -> new OnDemandScalingGovernor(policy, threshold); + } + + private abstract static class AbstractScalingGovernor implements ScalingGovernor { + protected final ScalingPolicy policy; + + AbstractScalingGovernor(ScalingPolicy policy) { + this.policy = policy; + } + } + + private static class PerformanceScalingGovernor extends AbstractScalingGovernor { + static final ScalingGovernorFactory FACTORY = PerformanceScalingGovernor::new; + + private PerformanceScalingGovernor(ScalingPolicy policy) { + super(policy); + } + + @Override + public void onStart() { + policy.setTarget(policy.getMax()); + } + } + + private static class PowerSaveScalingGovernor extends AbstractScalingGovernor { + static final ScalingGovernorFactory FACTORY = PowerSaveScalingGovernor::new; + + private PowerSaveScalingGovernor(ScalingPolicy policy) { + super(policy); + } + + @Override + public void onStart() { + policy.setTarget(policy.getMin()); + } + } + + private static class ConservativeScalingGovernor extends AbstractScalingGovernor { + private final double threshold; + private final double stepSize; + private double previousLoad; + + private ConservativeScalingGovernor(ScalingPolicy policy, double threshold, double stepSize) { + super(policy); + + this.threshold = threshold; + this.previousLoad = threshold; + + if (stepSize < 0) { + // https://github.com/torvalds/linux/blob/master/drivers/cpufreq/cpufreq_conservative.c#L33 + this.stepSize = policy.getMax() * 0.05; + } else { + this.stepSize = Math.min(stepSize, policy.getMax()); + } + } + + @Override + public void onStart() { + policy.setTarget(policy.getMin()); + } + + @Override + public void onLimit(double load) { + final ScalingPolicy policy = this.policy; + double currentTarget = policy.getTarget(); + if (load > threshold) { + // Check for load increase (see: + // https://github.com/torvalds/linux/blob/master/drivers/cpufreq/cpufreq_conservative.c#L102) + double step = 0.0; + + if (load > previousLoad) { + step = stepSize; + } else if (load < previousLoad) { + step = -stepSize; + } + + double target = Math.min(Math.max(currentTarget + step, policy.getMin()), policy.getMax()); + policy.setTarget(target); + } + previousLoad = load; + } + } + + private static class OnDemandScalingGovernor extends AbstractScalingGovernor { + private final double threshold; + private final double multiplier; + + private OnDemandScalingGovernor(ScalingPolicy policy, double threshold) { + super(policy); + + this.threshold = threshold; + this.multiplier = (policy.getMax() - policy.getMin()) / 100; + } + + @Override + public void onStart() { + policy.setTarget(policy.getMin()); + } + + @Override + public void onLimit(double load) { + final ScalingPolicy policy = this.policy; + double target; + + if (load < threshold) { + /* Proportional scaling (see: https://github.com/torvalds/linux/blob/master/drivers/cpufreq/cpufreq_ondemand.c#L151). */ + target = policy.getMin() + load * multiplier; + } else { + target = policy.getMax(); + } + + policy.setTarget(target); + } + } +} diff --git a/opendc-simulator/opendc-simulator-compute/src/main/kotlin/org/opendc/simulator/compute/kernel/cpufreq/ScalingPolicy.kt b/opendc-simulator/opendc-simulator-compute/src/main/java/org/opendc/simulator/compute/kernel/cpufreq/ScalingPolicy.java index f9351896..0cdb7a0b 100644 --- a/opendc-simulator/opendc-simulator-compute/src/main/kotlin/org/opendc/simulator/compute/kernel/cpufreq/ScalingPolicy.kt +++ b/opendc-simulator/opendc-simulator-compute/src/main/java/org/opendc/simulator/compute/kernel/cpufreq/ScalingPolicy.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2021 AtLarge Research + * Copyright (c) 2022 AtLarge Research * * Permission is hereby granted, free of charge, to any person obtaining a copy * of this software and associated documentation files (the "Software"), to deal @@ -20,32 +20,37 @@ * SOFTWARE. */ -package org.opendc.simulator.compute.kernel.cpufreq +package org.opendc.simulator.compute.kernel.cpufreq; -import org.opendc.simulator.compute.SimProcessingUnit +import org.opendc.simulator.compute.SimProcessingUnit; /** - * An interface that holds the state managed by a [ScalingGovernor] and used by the underlying machine to control the - * CPU frequencies. + * An interface that holds the state managed by a {@link ScalingGovernor} and used by the underlying machine to control + * the CPU frequencies. */ public interface ScalingPolicy { /** * The processing unit that is associated with this policy. */ - public val cpu: SimProcessingUnit + SimProcessingUnit getCpu(); /** - * The target frequency which the CPU should attempt to attain. + * Return the target frequency which the CPU should attempt to attain. */ - public var target: Double + double getTarget(); /** - * The minimum frequency to which the CPU may scale. + * Set the target frequency which the CPU should attempt to attain. */ - public val min: Double + void setTarget(double target); /** - * The maximum frequency to which the CPU may scale. + * Return the minimum frequency to which the CPU may scale. */ - public val max: Double + double getMin(); + + /** + * Return the maximum frequency to which the CPU may scale. + */ + double getMax(); } diff --git a/opendc-simulator/opendc-simulator-compute/src/main/java/org/opendc/simulator/compute/kernel/interference/VmInterferenceDomain.java b/opendc-simulator/opendc-simulator-compute/src/main/java/org/opendc/simulator/compute/kernel/interference/VmInterferenceDomain.java new file mode 100644 index 00000000..cc671379 --- /dev/null +++ b/opendc-simulator/opendc-simulator-compute/src/main/java/org/opendc/simulator/compute/kernel/interference/VmInterferenceDomain.java @@ -0,0 +1,136 @@ +/* + * Copyright (c) 2022 AtLarge Research + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ + +package org.opendc.simulator.compute.kernel.interference; + +import java.util.ArrayDeque; +import java.util.ArrayList; +import java.util.Collections; +import java.util.WeakHashMap; + +/** + * A domain where virtual machines may incur performance variability due to operating on the same resource and + * therefore causing interference. + */ +public final class VmInterferenceDomain { + /** + * A cache to maintain a mapping between the active profiles in this domain. + */ + private final WeakHashMap<VmInterferenceProfile, VmInterferenceMember> cache = new WeakHashMap<>(); + + /** + * The set of members active in this domain. + */ + private final ArrayList<VmInterferenceMember> activeKeys = new ArrayList<>(); + + /** + * Queue of participants that will be removed or added to the active groups. + */ + private final ArrayDeque<VmInterferenceMember> participants = new ArrayDeque<>(); + + /** + * Join this interference domain with the specified <code>profile</code> and return the {@link VmInterferenceMember} + * associated with the profile. If the member does not exist, it will be created. + */ + public VmInterferenceMember join(VmInterferenceProfile profile) { + return cache.computeIfAbsent(profile, (key) -> key.newMember(this)); + } + + /** + * Mark the specified <code>member</code> as active in this interference domain. + */ + void activate(VmInterferenceMember member) { + final ArrayList<VmInterferenceMember> activeKeys = this.activeKeys; + int pos = Collections.binarySearch(activeKeys, member); + if (pos < 0) { + activeKeys.add(-pos - 1, member); + } + + computeActiveGroups(activeKeys, member); + } + + /** + * Mark the specified <code>member</code> as inactive in this interference domain. + */ + void deactivate(VmInterferenceMember member) { + final ArrayList<VmInterferenceMember> activeKeys = this.activeKeys; + activeKeys.remove(member); + computeActiveGroups(activeKeys, member); + } + + /** + * (Re-)compute the active groups. + */ + private void computeActiveGroups(ArrayList<VmInterferenceMember> activeKeys, VmInterferenceMember member) { + if (activeKeys.isEmpty()) { + return; + } + + final int[] groups = member.membership; + final int[][] members = member.members; + final ArrayDeque<VmInterferenceMember> participants = this.participants; + + for (int group : groups) { + int[] groupMembers = members[group]; + + int i = 0; + int j = 0; + int intersection = 0; + + // Compute the intersection of the group members and the current active members + while (i < groupMembers.length && j < activeKeys.size()) { + int l = groupMembers[i]; + final VmInterferenceMember rightEntry = activeKeys.get(j); + int r = rightEntry.id; + + if (l < r) { + i++; + } else if (l > r) { + j++; + } else { + if (++intersection > 1) { + rightEntry.addGroup(group); + } else { + participants.add(rightEntry); + } + + i++; + j++; + } + } + + while (true) { + VmInterferenceMember participant = participants.poll(); + + if (participant == null) { + break; + } + + if (intersection <= 1) { + participant.removeGroup(group); + } else { + participant.addGroup(group); + } + } + } + } +} diff --git a/opendc-simulator/opendc-simulator-compute/src/main/java/org/opendc/simulator/compute/kernel/interference/VmInterferenceMember.java b/opendc-simulator/opendc-simulator-compute/src/main/java/org/opendc/simulator/compute/kernel/interference/VmInterferenceMember.java new file mode 100644 index 00000000..64cd5077 --- /dev/null +++ b/opendc-simulator/opendc-simulator-compute/src/main/java/org/opendc/simulator/compute/kernel/interference/VmInterferenceMember.java @@ -0,0 +1,177 @@ +/* + * Copyright (c) 2022 AtLarge Research + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ + +package org.opendc.simulator.compute.kernel.interference; + +import java.util.Arrays; +import java.util.SplittableRandom; +import org.jetbrains.annotations.NotNull; + +/** + * A participant of an interference domain. + */ +public final class VmInterferenceMember implements Comparable<VmInterferenceMember> { + private final VmInterferenceDomain domain; + private final VmInterferenceModel model; + final int id; + final int[] membership; + final int[][] members; + private final double[] targets; + private final double[] scores; + + private int[] groups = new int[2]; + private int groupsSize = 0; + + private int refCount = 0; + + VmInterferenceMember( + VmInterferenceDomain domain, + VmInterferenceModel model, + int id, + int[] membership, + int[][] members, + double[] targets, + double[] scores) { + this.domain = domain; + this.model = model; + this.id = id; + this.membership = membership; + this.members = members; + this.targets = targets; + this.scores = scores; + } + + /** + * Mark this member as active in this interference domain. + */ + public void activate() { + if (refCount++ <= 0) { + domain.activate(this); + } + } + + /** + * Mark this member as inactive in this interference domain. + */ + public void deactivate() { + if (--refCount <= 0) { + domain.deactivate(this); + } + } + + /** + * Compute the performance score of the member in this interference domain. + * + * @param random The source of randomness to apply when computing the performance score. + * @param load The overall load on the interference domain. + * @return A score representing the performance score to be applied to the member, with 1 + * meaning no influence, <1 means that performance degrades, and >1 means that performance improves. + */ + public double apply(SplittableRandom random, double load) { + int groupsSize = this.groupsSize; + + if (groupsSize == 0) { + return 1.0; + } + + int[] groups = this.groups; + double[] targets = this.targets; + + int low = 0; + int high = groupsSize - 1; + int group = -1; + + // Perform binary search over the groups based on target load + while (low <= high) { + int mid = low + high >>> 1; + int midGroup = groups[mid]; + double target = targets[midGroup]; + + if (target < load) { + low = mid + 1; + group = midGroup; + } else if (target > load) { + high = mid - 1; + } else { + group = midGroup; + break; + } + } + + if (group >= 0 && random.nextInt(members[group].length) == 0) { + return scores[group]; + } + + return 1.0; + } + + /** + * Add an active group to this member. + */ + void addGroup(int group) { + int[] groups = this.groups; + int groupsSize = this.groupsSize; + int pos = Arrays.binarySearch(groups, 0, groupsSize, group); + + if (pos >= 0) { + return; + } + + int idx = -pos - 1; + + if (groups.length == groupsSize) { + int newSize = groupsSize + (groupsSize >> 1); + groups = Arrays.copyOf(groups, newSize); + this.groups = groups; + } + + System.arraycopy(groups, idx, groups, idx + 1, groupsSize - idx); + groups[idx] = group; + this.groupsSize += 1; + } + + /** + * Remove an active group from this member. + */ + void removeGroup(int group) { + int[] groups = this.groups; + int groupsSize = this.groupsSize; + int pos = Arrays.binarySearch(groups, 0, groupsSize, group); + + if (pos < 0) { + return; + } + + System.arraycopy(groups, pos + 1, groups, pos, groupsSize - pos - 1); + this.groupsSize -= 1; + } + + @Override + public int compareTo(@NotNull VmInterferenceMember member) { + int cmp = Integer.compare(model.hashCode(), member.model.hashCode()); + if (cmp != 0) { + return cmp; + } + + return Integer.compare(id, member.id); + } +} diff --git a/opendc-simulator/opendc-simulator-compute/src/main/java/org/opendc/simulator/compute/kernel/interference/VmInterferenceModel.java b/opendc-simulator/opendc-simulator-compute/src/main/java/org/opendc/simulator/compute/kernel/interference/VmInterferenceModel.java new file mode 100644 index 00000000..e2093266 --- /dev/null +++ b/opendc-simulator/opendc-simulator-compute/src/main/java/org/opendc/simulator/compute/kernel/interference/VmInterferenceModel.java @@ -0,0 +1,185 @@ +/* + * Copyright (c) 2022 AtLarge Research + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ + +package org.opendc.simulator.compute.kernel.interference; + +import java.util.ArrayList; +import java.util.Arrays; +import java.util.Comparator; +import java.util.HashMap; +import java.util.Map; +import java.util.Set; +import java.util.TreeMap; +import java.util.TreeSet; +import org.jetbrains.annotations.Nullable; + +/** + * An interference model that models the resource interference between virtual machines on a host. + */ +public final class VmInterferenceModel { + private final Map<String, Integer> idMapping; + private final int[][] members; + private final int[][] membership; + private final double[] targets; + private final double[] scores; + + private VmInterferenceModel( + Map<String, Integer> idMapping, int[][] members, int[][] membership, double[] targets, double[] scores) { + this.idMapping = idMapping; + this.members = members; + this.membership = membership; + this.targets = targets; + this.scores = scores; + } + + /** + * Create a {@link Builder} for constructing a {@link VmInterferenceModel}. + */ + public static Builder builder() { + return new Builder(256); + } + + /** + * Return the {@link VmInterferenceProfile} associated with the specified <code>id</code>. + * + * @param id The identifier of the virtual machine. + * @return A {@link VmInterferenceProfile} representing the virtual machine as part of interference model or + * <code>null</code> if there is no profile for the virtual machine. + */ + @Nullable + public VmInterferenceProfile getProfile(String id) { + Integer intId = idMapping.get(id); + if (intId == null) { + return null; + } + return new VmInterferenceProfile(this, intId, membership[intId], members, targets, scores); + } + + /** + * Builder class for a {@link VmInterferenceModel}. + */ + public static final class Builder { + private double[] targets; + private double[] scores; + private final ArrayList<Set<String>> members; + private final TreeSet<String> ids; + private int size; + + private Builder(int initialCapacity) { + this.targets = new double[initialCapacity]; + this.scores = new double[initialCapacity]; + this.members = new ArrayList<>(initialCapacity); + this.ids = new TreeSet<>(); + } + + /** + * Add the specified group to the model. + */ + public Builder addGroup(Set<String> members, double targetLoad, double score) { + int size = this.size; + + if (size == targets.length) { + grow(); + } + + targets[size] = targetLoad; + scores[size] = score; + this.members.add(members); + ids.addAll(members); + + this.size++; + + return this; + } + + /** + * Build the {@link VmInterferenceModel}. + */ + public VmInterferenceModel build() { + int size = this.size; + double[] targets = this.targets; + double[] scores = this.scores; + ArrayList<Set<String>> members = this.members; + + Integer[] indices = new Integer[size]; + Arrays.setAll(indices, (i) -> i); + Arrays.sort( + indices, + Comparator.comparingDouble((Integer l) -> targets[l]) + .thenComparingDouble(l -> scores[l]) + .thenComparingInt(l -> l)); + + double[] newTargets = new double[size]; + double[] newScores = new double[size]; + int[][] newMembers = new int[size][]; + + int nextId = 0; + + Map<String, Integer> idMapping = new HashMap<>(); + TreeMap<String, ArrayList<Integer>> membership = new TreeMap<>(); + for (String id : ids) { + idMapping.put(id, nextId++); + membership.put(id, new ArrayList<>()); + } + + for (int group = 0; group < indices.length; group++) { + int j = indices[group]; + newTargets[group] = targets[j]; + newScores[group] = scores[j]; + + Set<String> groupMembers = members.get(j); + int[] newGroupMembers = new int[groupMembers.size()]; + int k = 0; + + for (String groupMember : groupMembers) { + newGroupMembers[k++] = idMapping.get(groupMember); + } + + Arrays.sort(newGroupMembers); + newMembers[group] = newGroupMembers; + + for (String member : groupMembers) { + membership.get(member).add(group); + } + } + + int[][] newMembership = new int[membership.size()][]; + int k = 0; + for (ArrayList<Integer> value : membership.values()) { + newMembership[k++] = value.stream().mapToInt(i -> i).toArray(); + } + + return new VmInterferenceModel(idMapping, newMembers, newMembership, newTargets, newScores); + } + + /** + * Helper function to grow the capacity of the internal arrays. + */ + private void grow() { + int oldSize = targets.length; + int newSize = oldSize + (oldSize >> 1); + + targets = Arrays.copyOf(targets, newSize); + scores = Arrays.copyOf(scores, newSize); + } + } +} diff --git a/opendc-simulator/opendc-simulator-compute/src/main/kotlin/org/opendc/simulator/compute/kernel/interference/VmInterferenceProfile.kt b/opendc-simulator/opendc-simulator-compute/src/main/java/org/opendc/simulator/compute/kernel/interference/VmInterferenceProfile.java index 004dbd07..3f0c0a88 100644 --- a/opendc-simulator/opendc-simulator-compute/src/main/kotlin/org/opendc/simulator/compute/kernel/interference/VmInterferenceProfile.kt +++ b/opendc-simulator/opendc-simulator-compute/src/main/java/org/opendc/simulator/compute/kernel/interference/VmInterferenceProfile.java @@ -20,32 +20,41 @@ * SOFTWARE. */ -package org.opendc.simulator.compute.kernel.interference +package org.opendc.simulator.compute.kernel.interference; /** * A profile of a particular virtual machine describing its interference pattern with other virtual machines. - * - * @param model The model to which this profile belongs. - * @property id The identifier of the profile inside the model. - * @property membership The membership of the profile in the groups. - * @param members The members in the model. - * @param targets The targets in the model. - * @param scores The scores in the model. */ -public class VmInterferenceProfile internal constructor( - private val model: VmInterferenceModel, - private val id: Int, - private val membership: IntArray, - private val members: Array<IntArray>, - private val targets: DoubleArray, - private val scores: DoubleArray -) { +public final class VmInterferenceProfile { + private final VmInterferenceModel model; + private final int id; + private final int[] membership; + private final int[][] members; + private final double[] targets; + private final double[] scores; + /** - * Create a new [VmInterferenceMember] based on this profile for the specified [domain]. + * Construct a {@link VmInterferenceProfile}. */ - internal fun newMember(domain: VmInterferenceDomain): VmInterferenceMember { - return VmInterferenceMember(domain, model, id, membership, members, targets, scores) + VmInterferenceProfile( + VmInterferenceModel model, int id, int[] membership, int[][] members, double[] targets, double[] scores) { + this.model = model; + this.id = id; + this.membership = membership; + this.members = members; + this.targets = targets; + this.scores = scores; } - override fun toString(): String = "VmInterferenceProfile[id=$id]" + /** + * Create a new {@link VmInterferenceMember} based on this profile for the specified <code>domain</code>. + */ + VmInterferenceMember newMember(VmInterferenceDomain domain) { + return new VmInterferenceMember(domain, model, id, membership, members, targets, scores); + } + + @Override + public String toString() { + return "VmInterferenceProfile[id=" + id + "]"; + } } diff --git a/opendc-simulator/opendc-simulator-compute/src/main/java/org/opendc/simulator/compute/model/MachineModel.java b/opendc-simulator/opendc-simulator-compute/src/main/java/org/opendc/simulator/compute/model/MachineModel.java new file mode 100644 index 00000000..2c625fce --- /dev/null +++ b/opendc-simulator/opendc-simulator-compute/src/main/java/org/opendc/simulator/compute/model/MachineModel.java @@ -0,0 +1,149 @@ +/* + * Copyright (c) 2022 AtLarge Research + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ + +package org.opendc.simulator.compute.model; + +import java.util.ArrayList; +import java.util.Collections; +import java.util.List; +import java.util.Objects; + +/** + * A description of the physical or virtual machine on which a bootable image runs. + */ +public final class MachineModel { + private final List<ProcessingUnit> cpus; + private final List<MemoryUnit> memory; + private final List<NetworkAdapter> net; + private final List<StorageDevice> storage; + + /** + * Construct a {@link MachineModel} instance. + * + * @param cpus The list of processing units available to the image. + * @param memory The list of memory units available to the image. + * @param net A list of network adapters available to the machine. + * @param storage A list of storage devices available to the machine. + */ + public MachineModel( + Iterable<ProcessingUnit> cpus, + Iterable<MemoryUnit> memory, + Iterable<NetworkAdapter> net, + Iterable<StorageDevice> storage) { + this.cpus = new ArrayList<>(); + cpus.forEach(this.cpus::add); + + this.memory = new ArrayList<>(); + memory.forEach(this.memory::add); + + this.net = new ArrayList<>(); + net.forEach(this.net::add); + + this.storage = new ArrayList<>(); + storage.forEach(this.storage::add); + } + + /** + * Construct a {@link MachineModel} instance. + * + * @param cpus The list of processing units available to the image. + * @param memory The list of memory units available to the image. + */ + public MachineModel(Iterable<ProcessingUnit> cpus, Iterable<MemoryUnit> memory) { + this(cpus, memory, Collections.emptyList(), Collections.emptyList()); + } + + /** + * Optimize the [MachineModel] by merging all resources of the same type into a single resource with the combined + * capacity. Such configurations can be simulated more efficiently by OpenDC. + */ + public MachineModel optimize() { + ProcessingUnit originalCpu = cpus.get(0); + + double freq = 0.0; + for (ProcessingUnit cpu : cpus) { + freq += cpu.getFrequency(); + } + + ProcessingNode originalNode = originalCpu.getNode(); + ProcessingNode processingNode = new ProcessingNode( + originalNode.getVendor(), originalNode.getModelName(), originalNode.getArchitecture(), 1); + ProcessingUnit processingUnit = new ProcessingUnit(processingNode, originalCpu.getId(), freq); + + long memorySize = 0; + for (MemoryUnit mem : memory) { + memorySize += mem.getSize(); + } + MemoryUnit memoryUnit = new MemoryUnit("Generic", "Generic", 3200.0, memorySize); + + return new MachineModel(List.of(processingUnit), List.of(memoryUnit)); + } + + /** + * Return the processing units of this machine. + */ + public List<ProcessingUnit> getCpus() { + return Collections.unmodifiableList(cpus); + } + + /** + * Return the memory units of this machine. + */ + public List<MemoryUnit> getMemory() { + return Collections.unmodifiableList(memory); + } + + /** + * Return the network adapters of this machine. + */ + public List<NetworkAdapter> getNetwork() { + return Collections.unmodifiableList(net); + } + + /** + * Return the storage devices of this machine. + */ + public List<StorageDevice> getStorage() { + return Collections.unmodifiableList(storage); + } + + @Override + public boolean equals(Object o) { + if (this == o) return true; + if (o == null || getClass() != o.getClass()) return false; + MachineModel that = (MachineModel) o; + return cpus.equals(that.cpus) + && memory.equals(that.memory) + && net.equals(that.net) + && storage.equals(that.storage); + } + + @Override + public int hashCode() { + return Objects.hash(cpus, memory, net, storage); + } + + @Override + public String toString() { + return "MachineModel[cpus=" + cpus + ",memory=" + memory + ",net=" + net + ",storage=" + storage + "]"; + } +} diff --git a/opendc-simulator/opendc-simulator-compute/src/main/java/org/opendc/simulator/compute/model/MemoryUnit.java b/opendc-simulator/opendc-simulator-compute/src/main/java/org/opendc/simulator/compute/model/MemoryUnit.java new file mode 100644 index 00000000..4250f5a2 --- /dev/null +++ b/opendc-simulator/opendc-simulator-compute/src/main/java/org/opendc/simulator/compute/model/MemoryUnit.java @@ -0,0 +1,100 @@ +/* + * Copyright (c) 2022 AtLarge Research + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ + +package org.opendc.simulator.compute.model; + +import java.util.Objects; + +/** + * A memory unit of a compute resource, either virtual or physical. + */ +public final class MemoryUnit { + private final String vendor; + private final String modelName; + private final double speed; + private final long size; + + /** + * Construct a {@link ProcessingNode} instance. + * + * @param vendor The vendor of the storage device. + * @param modelName The model name of the device. + * @param speed The access speed of the memory in MHz. + * @param size The size of the memory unit in MBs. + */ + public MemoryUnit(String vendor, String modelName, double speed, long size) { + this.vendor = vendor; + this.modelName = modelName; + this.speed = speed; + this.size = size; + } + + /** + * Return the vendor of the storage device. + */ + public String getVendor() { + return vendor; + } + + /** + * Return the model name of the device. + */ + public String getModelName() { + return modelName; + } + + /** + * Return the access speed of the memory in MHz. + */ + public double getSpeed() { + return speed; + } + + /** + * Return the size of the memory unit in MBs. + */ + public long getSize() { + return size; + } + + @Override + public boolean equals(Object o) { + if (this == o) return true; + if (o == null || getClass() != o.getClass()) return false; + MemoryUnit that = (MemoryUnit) o; + return Double.compare(that.speed, speed) == 0 + && size == that.size + && vendor.equals(that.vendor) + && modelName.equals(that.modelName); + } + + @Override + public int hashCode() { + return Objects.hash(vendor, modelName, speed, size); + } + + @Override + public String toString() { + return "ProcessingNode[vendor='" + vendor + "',modelName='" + modelName + "',speed=" + speed + "MHz,size=" + + size + "MB]"; + } +} diff --git a/opendc-simulator/opendc-simulator-compute/src/main/java/org/opendc/simulator/compute/model/NetworkAdapter.java b/opendc-simulator/opendc-simulator-compute/src/main/java/org/opendc/simulator/compute/model/NetworkAdapter.java new file mode 100644 index 00000000..ff3daa40 --- /dev/null +++ b/opendc-simulator/opendc-simulator-compute/src/main/java/org/opendc/simulator/compute/model/NetworkAdapter.java @@ -0,0 +1,88 @@ +/* + * Copyright (c) 2022 AtLarge Research + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ + +package org.opendc.simulator.compute.model; + +import java.util.Objects; + +/** + * A description of a network adapter + */ +public final class NetworkAdapter { + private final String vendor; + private final String modelName; + private final double bandwidth; + + /** + * Construct a {@link NetworkAdapter} instance. + * + * @param vendor The vendor of the storage device. + * @param modelName The model name of the device. + * @param bandwidth The bandwidth of the network adapter in Mbps. + */ + public NetworkAdapter(String vendor, String modelName, double bandwidth) { + this.vendor = vendor; + this.modelName = modelName; + this.bandwidth = bandwidth; + } + + /** + * Return the vendor of the storage device. + */ + public String getVendor() { + return vendor; + } + + /** + * Return the model name of the device. + */ + public String getModelName() { + return modelName; + } + + /** + * Return the bandwidth of the network adapter in Mbps. + */ + public double getBandwidth() { + return bandwidth; + } + + @Override + public boolean equals(Object o) { + if (this == o) return true; + if (o == null || getClass() != o.getClass()) return false; + NetworkAdapter that = (NetworkAdapter) o; + return Double.compare(that.bandwidth, bandwidth) == 0 + && vendor.equals(that.vendor) + && modelName.equals(that.modelName); + } + + @Override + public int hashCode() { + return Objects.hash(vendor, modelName, bandwidth); + } + + @Override + public String toString() { + return "NetworkAdapter[vendor='" + vendor + "',modelName='" + modelName + "',bandwidth=" + bandwidth + "Mbps]"; + } +} diff --git a/opendc-simulator/opendc-simulator-compute/src/main/java/org/opendc/simulator/compute/model/ProcessingNode.java b/opendc-simulator/opendc-simulator-compute/src/main/java/org/opendc/simulator/compute/model/ProcessingNode.java new file mode 100644 index 00000000..01a87b96 --- /dev/null +++ b/opendc-simulator/opendc-simulator-compute/src/main/java/org/opendc/simulator/compute/model/ProcessingNode.java @@ -0,0 +1,100 @@ +/* + * Copyright (c) 2022 AtLarge Research + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ + +package org.opendc.simulator.compute.model; + +import java.util.Objects; + +/** + * A processing node/package/socket containing possibly several CPU cores. + */ +public final class ProcessingNode { + private final String vendor; + private final String modelName; + private final String arch; + private final int coreCount; + + /** + * Construct a {@link ProcessingNode} instance. + * + * @param vendor The vendor of the storage device. + * @param modelName The model name of the device. + * @param arch The micro-architecture of the processor node. + * @param coreCount The number of logical CPUs in the processor node. + */ + public ProcessingNode(String vendor, String modelName, String arch, int coreCount) { + this.vendor = vendor; + this.modelName = modelName; + this.arch = arch; + this.coreCount = coreCount; + } + + /** + * Return the vendor of the storage device. + */ + public String getVendor() { + return vendor; + } + + /** + * Return the model name of the device. + */ + public String getModelName() { + return modelName; + } + + /** + * Return the micro-architecture of the processor node. + */ + public String getArchitecture() { + return arch; + } + + /** + * Return the number of logical CPUs in the processor node. + */ + public int getCoreCount() { + return coreCount; + } + + @Override + public boolean equals(Object o) { + if (this == o) return true; + if (o == null || getClass() != o.getClass()) return false; + ProcessingNode that = (ProcessingNode) o; + return coreCount == that.coreCount + && vendor.equals(that.vendor) + && modelName.equals(that.modelName) + && arch.equals(that.arch); + } + + @Override + public int hashCode() { + return Objects.hash(vendor, modelName, arch, coreCount); + } + + @Override + public String toString() { + return "ProcessingNode[vendor='" + vendor + "',modelName='" + modelName + "',arch=" + arch + ",coreCount=" + + coreCount + "]"; + } +} diff --git a/opendc-simulator/opendc-simulator-compute/src/main/java/org/opendc/simulator/compute/model/ProcessingUnit.java b/opendc-simulator/opendc-simulator-compute/src/main/java/org/opendc/simulator/compute/model/ProcessingUnit.java new file mode 100644 index 00000000..51a045d1 --- /dev/null +++ b/opendc-simulator/opendc-simulator-compute/src/main/java/org/opendc/simulator/compute/model/ProcessingUnit.java @@ -0,0 +1,86 @@ +/* + * Copyright (c) 2022 AtLarge Research + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ + +package org.opendc.simulator.compute.model; + +import java.util.Objects; + +/** + * A single logical compute unit of processor node, either virtual or physical. + */ +public final class ProcessingUnit { + private final ProcessingNode node; + private final int id; + private final double frequency; + + /** + * Construct a {@link ProcessingUnit} instance. + * + * @param node The processing node containing the CPU core. + * @param id The identifier of the CPU core within the processing node. + * @param frequency The clock rate of the CPU in MHz. + */ + public ProcessingUnit(ProcessingNode node, int id, double frequency) { + this.node = node; + this.id = id; + this.frequency = frequency; + } + + /** + * Return the processing node containing the CPU core. + */ + public ProcessingNode getNode() { + return node; + } + + /** + * Return the identifier of the CPU core within the processing node. + */ + public int getId() { + return id; + } + + /** + * Return the clock rate of the CPU in MHz. + */ + public double getFrequency() { + return frequency; + } + + @Override + public boolean equals(Object o) { + if (this == o) return true; + if (o == null || getClass() != o.getClass()) return false; + ProcessingUnit that = (ProcessingUnit) o; + return id == that.id && Double.compare(that.frequency, frequency) == 0 && Objects.equals(node, that.node); + } + + @Override + public int hashCode() { + return Objects.hash(node, id, frequency); + } + + @Override + public String toString() { + return "ProcessingUnit[node=" + node + ",id=" + id + ",frequency=" + frequency + "]"; + } +} diff --git a/opendc-simulator/opendc-simulator-compute/src/main/java/org/opendc/simulator/compute/model/StorageDevice.java b/opendc-simulator/opendc-simulator-compute/src/main/java/org/opendc/simulator/compute/model/StorageDevice.java new file mode 100644 index 00000000..549ccc7e --- /dev/null +++ b/opendc-simulator/opendc-simulator-compute/src/main/java/org/opendc/simulator/compute/model/StorageDevice.java @@ -0,0 +1,112 @@ +/* + * Copyright (c) 2022 AtLarge Research + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ + +package org.opendc.simulator.compute.model; + +import java.util.Objects; + +/** + * Model for a physical storage device attached to a machine. + */ +public final class StorageDevice { + private final String vendor; + private final String modelName; + private final double capacity; + private final double readBandwidth; + private final double writeBandwidth; + + /** + * Construct a {@link StorageDevice} instance. + * + * @param vendor The vendor of the storage device. + * @param modelName The model name of the device. + * @param capacity The capacity of the device. + * @param readBandwidth The read bandwidth of the device in MBps. + * @param writeBandwidth The write bandwidth of the device in MBps. + */ + public StorageDevice( + String vendor, String modelName, double capacity, double readBandwidth, double writeBandwidth) { + this.vendor = vendor; + this.modelName = modelName; + this.capacity = capacity; + this.readBandwidth = readBandwidth; + this.writeBandwidth = writeBandwidth; + } + + /** + * Return the vendor of the storage device. + */ + public String getVendor() { + return vendor; + } + + /** + * Return the model name of the device. + */ + public String getModelName() { + return modelName; + } + + /** + * Return the capacity of the device. + */ + public double getCapacity() { + return capacity; + } + + /** + * Return the read bandwidth of the device in MBps. + */ + public double getReadBandwidth() { + return readBandwidth; + } + + /** + * Return the write bandwidth of the device in MBps. + */ + public double getWriteBandwidth() { + return writeBandwidth; + } + + @Override + public boolean equals(Object o) { + if (this == o) return true; + if (o == null || getClass() != o.getClass()) return false; + StorageDevice that = (StorageDevice) o; + return Double.compare(that.capacity, capacity) == 0 + && Double.compare(that.readBandwidth, readBandwidth) == 0 + && Double.compare(that.writeBandwidth, writeBandwidth) == 0 + && vendor.equals(that.vendor) + && modelName.equals(that.modelName); + } + + @Override + public int hashCode() { + return Objects.hash(vendor, modelName, capacity, readBandwidth, writeBandwidth); + } + + @Override + public String toString() { + return "StorageDevice[vendor='" + vendor + "',modelName='" + modelName + "',capacity=" + capacity + + ",readBandwidth=" + readBandwidth + ",writeBandwidth=" + writeBandwidth + "]"; + } +} diff --git a/opendc-simulator/opendc-simulator-compute/src/main/kotlin/org/opendc/simulator/compute/power/PowerModel.kt b/opendc-simulator/opendc-simulator-compute/src/main/java/org/opendc/simulator/compute/power/CpuPowerModel.java index decb2420..e023d098 100644 --- a/opendc-simulator/opendc-simulator-compute/src/main/kotlin/org/opendc/simulator/compute/power/PowerModel.kt +++ b/opendc-simulator/opendc-simulator-compute/src/main/java/org/opendc/simulator/compute/power/CpuPowerModel.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2021 AtLarge Research + * Copyright (c) 2022 AtLarge Research * * Permission is hereby granted, free of charge, to any person obtaining a copy * of this software and associated documentation files (the "Software"), to deal @@ -20,19 +20,19 @@ * SOFTWARE. */ -package org.opendc.simulator.compute.power +package org.opendc.simulator.compute.power; -import org.opendc.simulator.compute.SimMachine +import org.opendc.simulator.compute.SimMachine; /** - * A model for estimating the power usage of a [SimMachine]. + * A model for estimating the power usage of a {@link SimMachine} based on the CPU usage. */ -public interface PowerModel { +public interface CpuPowerModel { /** * Computes CPU power consumption for each host. * * @param utilization The CPU utilization percentage. - * @return A [Double] value of CPU power consumption. + * @return A double value of CPU power consumption (in W). */ - public fun computePower(utilization: Double): Double + double computePower(double utilization); } diff --git a/opendc-simulator/opendc-simulator-compute/src/main/java/org/opendc/simulator/compute/power/CpuPowerModels.java b/opendc-simulator/opendc-simulator-compute/src/main/java/org/opendc/simulator/compute/power/CpuPowerModels.java new file mode 100644 index 00000000..5d3d936b --- /dev/null +++ b/opendc-simulator/opendc-simulator-compute/src/main/java/org/opendc/simulator/compute/power/CpuPowerModels.java @@ -0,0 +1,330 @@ +/* + * Copyright (c) 2022 AtLarge Research + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ + +package org.opendc.simulator.compute.power; + +import java.util.Arrays; + +/** + * A collection {@link CpuPowerModel} implementations. + */ +public class CpuPowerModels { + private CpuPowerModels() {} + + /** + * Construct a constant {@link CpuPowerModel}. + * + * @param power The power consumption fo the server at all times (in W). + */ + public static CpuPowerModel constant(double power) { + return new ConstantPowerModel(power); + } + + /** + * Construct a square root {@link CpuPowerModel} that is adapted from CloudSim. + * + * @param maxPower The maximum power draw of the server in W. + * @param idlePower The power draw of the server at its lowest utilization level in W. + */ + public static CpuPowerModel sqrt(double maxPower, double idlePower) { + return new SqrtPowerModel(maxPower, idlePower); + } + + /** + * Construct a linear {@link CpuPowerModel} that is adapted from CloudSim. + * + * @param maxPower The maximum power draw of the server in W. + * @param idlePower The power draw of the server at its lowest utilization level in W. + */ + public static CpuPowerModel linear(double maxPower, double idlePower) { + return new LinearPowerModel(maxPower, idlePower); + } + + /** + * Construct a square {@link CpuPowerModel} that is adapted from CloudSim. + * + * @param maxPower The maximum power draw of the server in W. + * @param idlePower The power draw of the server at its lowest utilization level in W. + */ + public static CpuPowerModel square(double maxPower, double idlePower) { + return new SquarePowerModel(maxPower, idlePower); + } + + /** + * Construct a cubic {@link CpuPowerModel} that is adapted from CloudSim. + * + * @param maxPower The maximum power draw of the server in W. + * @param idlePower The power draw of the server at its lowest utilization level in W. + */ + public static CpuPowerModel cubic(double maxPower, double idlePower) { + return new CubicPowerModel(maxPower, idlePower); + } + + /** + * Construct a {@link CpuPowerModel} that minimizes the mean squared error (MSE) + * to the actual power measurement by tuning the calibration parameter. + * + * @param maxPower The maximum power draw of the server in W. + * @param idlePower The power draw of the server at its lowest utilization level in W. + * @param calibrationFactor The parameter set to minimize the MSE. + * @see <a href="https://dl.acm.org/doi/abs/10.1145/1273440.1250665"> + * Fan et al., Power provisioning for a warehouse-sized computer, ACM SIGARCH'07</a> + */ + public static CpuPowerModel mse(double maxPower, double idlePower, double calibrationFactor) { + return new MsePowerModel(maxPower, idlePower, calibrationFactor); + } + + /** + * Construct an asymptotic {@link CpuPowerModel} adapted from GreenCloud. + * + * @param maxPower The maximum power draw of the server in W. + * @param idlePower The power draw of the server at its lowest utilization level in W. + * @param asymUtil A utilization level at which the server attains asymptotic, + * i.e., close to linear power consumption versus the offered load. + * For most of the CPUs,a is in [0.2, 0.5]. + * @param dvfs A flag indicates whether DVFS is enabled. + */ + public static CpuPowerModel asymptotic(double maxPower, double idlePower, double asymUtil, boolean dvfs) { + return new AsymptoticPowerModel(maxPower, idlePower, asymUtil, dvfs); + } + + /** + * Construct a linear interpolation model {@link CpuPowerModel} that is adapted from CloudSim. + * + * <p> + * The power consumption is linearly interpolated over the given power levels. In case of two values, the first + * represents 0% utilization, while the last value represent 100% utilization. + * + * @param powerLevels An array of power consumption steps (in W) for a specific CPU utilization. + * @see <a href="http://www.spec.org/power_ssj2008/results/res2011q1/">Machines used in the SPEC benchmark</a> + */ + public static CpuPowerModel interpolate(double... powerLevels) { + return new InterpolationPowerModel(powerLevels.clone()); + } + + /** + * Decorate an existing {@link CpuPowerModel} to ensure that zero power consumption is reported when there is no + * utilization. + * + * @param delegate The existing {@link CpuPowerModel} to decorate. + */ + public static CpuPowerModel zeroIdle(CpuPowerModel delegate) { + return new ZeroIdlePowerDecorator(delegate); + } + + private static final class ConstantPowerModel implements CpuPowerModel { + private final double power; + + ConstantPowerModel(double power) { + this.power = power; + } + + @Override + public double computePower(double utilization) { + return power; + } + + @Override + public String toString() { + return "ConstantPowerModel[power=" + power + "]"; + } + } + + private abstract static class MaxIdlePowerModel implements CpuPowerModel { + protected final double maxPower; + protected final double idlePower; + + MaxIdlePowerModel(double maxPower, double idlePower) { + this.maxPower = maxPower; + this.idlePower = idlePower; + } + + @Override + public String toString() { + return getClass().getSimpleName() + "[max=" + maxPower + ",idle=" + idlePower + "]"; + } + } + + private static final class SqrtPowerModel extends MaxIdlePowerModel { + private final double factor; + + SqrtPowerModel(double maxPower, double idlePower) { + super(maxPower, idlePower); + this.factor = (maxPower - idlePower) / Math.sqrt(100); + } + + @Override + public double computePower(double utilization) { + return idlePower + factor * Math.sqrt(utilization * 100); + } + } + + private static final class LinearPowerModel extends MaxIdlePowerModel { + private final double factor; + + LinearPowerModel(double maxPower, double idlePower) { + super(maxPower, idlePower); + this.factor = (maxPower - idlePower) / 100; + } + + @Override + public double computePower(double utilization) { + return idlePower + factor * utilization * 100; + } + } + + private static final class SquarePowerModel extends MaxIdlePowerModel { + private final double factor; + + SquarePowerModel(double maxPower, double idlePower) { + super(maxPower, idlePower); + this.factor = (maxPower - idlePower) / Math.pow(100, 2); + } + + @Override + public double computePower(double utilization) { + return idlePower + factor * Math.pow(utilization * 100, 2); + } + } + + private static final class CubicPowerModel extends MaxIdlePowerModel { + private final double factor; + + CubicPowerModel(double maxPower, double idlePower) { + super(maxPower, idlePower); + this.factor = (maxPower - idlePower) / Math.pow(100, 3); + } + + @Override + public double computePower(double utilization) { + return idlePower + factor * Math.pow(utilization * 100, 3); + } + } + + private static final class MsePowerModel extends MaxIdlePowerModel { + private final double calibrationFactor; + private final double factor; + + MsePowerModel(double maxPower, double idlePower, double calibrationFactor) { + super(maxPower, idlePower); + this.calibrationFactor = calibrationFactor; + this.factor = (maxPower - idlePower) / 100; + } + + @Override + public double computePower(double utilization) { + return idlePower + factor * (2 * utilization - Math.pow(utilization, calibrationFactor)) * 100; + } + + @Override + public String toString() { + return "MsePowerModel[max=" + maxPower + ",idle=" + idlePower + ",calibrationFactor=" + calibrationFactor + + "]"; + } + } + + private static final class AsymptoticPowerModel extends MaxIdlePowerModel { + private final double asymUtil; + private final boolean dvfs; + private final double factor; + + AsymptoticPowerModel(double maxPower, double idlePower, double asymUtil, boolean dvfs) { + super(maxPower, idlePower); + this.asymUtil = asymUtil; + this.dvfs = dvfs; + this.factor = (maxPower - idlePower) / 100; + } + + @Override + public double computePower(double utilization) { + if (dvfs) { + return idlePower + + (factor * 100) + / 2 + * (1 + + Math.pow(utilization, 3) + - Math.pow(Math.E, -Math.pow(utilization, 3) / asymUtil)); + } else { + return idlePower + (factor * 100) / 2 * (1 + utilization - Math.pow(Math.E, -utilization / asymUtil)); + } + } + + @Override + public String toString() { + return "AsymptoticPowerModel[max=" + maxPower + ",idle=" + idlePower + ",asymUtil=" + asymUtil + ",dvfs=" + + dvfs + "]"; + } + } + + private static final class InterpolationPowerModel implements CpuPowerModel { + private final double[] powerLevels; + + InterpolationPowerModel(double[] powerLevels) { + this.powerLevels = powerLevels; + } + + @Override + public double computePower(double utilization) { + final double[] powerLevels = this.powerLevels; + double clampedUtilization = Math.min(1.0, Math.max(0.0, utilization)); + + if (utilization % 0.1 == 0.0) { + return powerLevels[(int) (clampedUtilization * 10)]; + } + + int utilizationFlr = (int) Math.floor(clampedUtilization * 10); + int utilizationCil = (int) Math.ceil(clampedUtilization * 10); + double powerFlr = powerLevels[utilizationFlr]; + double powerCil = powerLevels[utilizationCil]; + double delta = (powerCil - powerFlr) / 10; + + return powerFlr + delta * (clampedUtilization - utilizationFlr / 10.0) * 100; + } + + @Override + public String toString() { + return "InterpolationPowerModel[levels=" + Arrays.toString(powerLevels) + "]"; + } + } + + private static final class ZeroIdlePowerDecorator implements CpuPowerModel { + private final CpuPowerModel delegate; + + ZeroIdlePowerDecorator(CpuPowerModel delegate) { + this.delegate = delegate; + } + + @Override + public double computePower(double utilization) { + if (utilization == 0.0) { + return 0.0; + } + + return delegate.computePower(utilization); + } + + @Override + public String toString() { + return "ZeroIdlePowerDecorator[delegate=" + delegate + "]"; + } + } +} diff --git a/opendc-simulator/opendc-simulator-compute/src/main/java/org/opendc/simulator/compute/workload/SimFlopsWorkload.java b/opendc-simulator/opendc-simulator-compute/src/main/java/org/opendc/simulator/compute/workload/SimFlopsWorkload.java new file mode 100644 index 00000000..255fd1b2 --- /dev/null +++ b/opendc-simulator/opendc-simulator-compute/src/main/java/org/opendc/simulator/compute/workload/SimFlopsWorkload.java @@ -0,0 +1,141 @@ +/* + * Copyright (c) 2022 AtLarge Research + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ + +package org.opendc.simulator.compute.workload; + +import java.util.List; +import org.opendc.simulator.compute.SimMachineContext; +import org.opendc.simulator.compute.SimProcessingUnit; +import org.opendc.simulator.flow2.FlowGraph; +import org.opendc.simulator.flow2.FlowStage; +import org.opendc.simulator.flow2.FlowStageLogic; +import org.opendc.simulator.flow2.OutPort; + +/** + * A {@link SimWorkload} that models applications as a static number of floating point operations executed on + * multiple cores of a compute resource. + */ +public class SimFlopsWorkload implements SimWorkload, FlowStageLogic { + private final long flops; + private final double utilization; + + private SimMachineContext ctx; + private FlowStage stage; + private OutPort[] outputs; + + private float remainingAmount; + private long lastUpdate; + + /** + * Construct a new {@link SimFlopsWorkload}. + * + * @param flops The number of floating point operations to perform for this task in MFLOPs. + * @param utilization A model of the CPU utilization of the application. + */ + public SimFlopsWorkload(long flops, double utilization) { + if (flops < 0) { + throw new IllegalArgumentException("Number of FLOPs must be positive"); + } else if (utilization <= 0.0 || utilization > 1.0) { + throw new IllegalArgumentException("Utilization must be in (0, 1]"); + } + + this.flops = flops; + this.utilization = utilization; + } + + @Override + public void onStart(SimMachineContext ctx) { + this.ctx = ctx; + + final FlowGraph graph = ctx.getGraph(); + final FlowStage stage = graph.newStage(this); + this.stage = stage; + + final List<? extends SimProcessingUnit> cpus = ctx.getCpus(); + final OutPort[] outputs = new OutPort[cpus.size()]; + this.outputs = outputs; + + for (int i = 0; i < cpus.size(); i++) { + final SimProcessingUnit cpu = cpus.get(i); + final OutPort output = stage.getOutlet("cpu" + i); + + graph.connect(output, cpu.getInput()); + outputs[i] = output; + } + + this.remainingAmount = flops; + this.lastUpdate = graph.getEngine().getClock().millis(); + } + + @Override + public void onStop(SimMachineContext ctx) { + this.ctx = null; + + final FlowStage stage = this.stage; + if (stage != null) { + this.stage = null; + stage.close(); + } + } + + @Override + public String toString() { + return "SimFlopsWorkload[FLOPs=" + flops + ",utilization=" + utilization + "]"; + } + + @Override + public long onUpdate(FlowStage ctx, long now) { + long lastUpdate = this.lastUpdate; + this.lastUpdate = now; + + long delta = Math.max(0, now - lastUpdate); + + float consumed = 0.f; + float limit = 0.f; + + for (final OutPort output : outputs) { + consumed += output.getRate() * delta; + + float outputLimit = (float) (output.getCapacity() * utilization); + limit += outputLimit; + + output.push(outputLimit); + } + consumed = (float) (consumed * 0.001); + + float remainingAmount = this.remainingAmount - consumed; + this.remainingAmount = remainingAmount; + + long duration = (long) Math.ceil(remainingAmount / limit * 1000); + + if (duration <= 0) { + final SimMachineContext machineContext = this.ctx; + if (machineContext != null) { + machineContext.shutdown(); + } + ctx.close(); + return Long.MAX_VALUE; + } + + return now + duration; + } +} diff --git a/opendc-simulator/opendc-simulator-compute/src/main/java/org/opendc/simulator/compute/workload/SimRuntimeWorkload.java b/opendc-simulator/opendc-simulator-compute/src/main/java/org/opendc/simulator/compute/workload/SimRuntimeWorkload.java new file mode 100644 index 00000000..c3380b31 --- /dev/null +++ b/opendc-simulator/opendc-simulator-compute/src/main/java/org/opendc/simulator/compute/workload/SimRuntimeWorkload.java @@ -0,0 +1,131 @@ +/* + * Copyright (c) 2022 AtLarge Research + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ + +package org.opendc.simulator.compute.workload; + +import java.util.List; +import org.opendc.simulator.compute.SimMachineContext; +import org.opendc.simulator.compute.SimProcessingUnit; +import org.opendc.simulator.flow2.FlowGraph; +import org.opendc.simulator.flow2.FlowStage; +import org.opendc.simulator.flow2.FlowStageLogic; +import org.opendc.simulator.flow2.OutPort; + +/** + * A [SimWorkload] that models application execution as a single duration. + */ +public class SimRuntimeWorkload implements SimWorkload, FlowStageLogic { + private final long duration; + private final double utilization; + + private SimMachineContext ctx; + private FlowStage stage; + private OutPort[] outputs; + + private long remainingDuration; + private long lastUpdate; + + /** + * Construct a new {@link SimRuntimeWorkload}. + * + * @param duration The duration of the workload in milliseconds. + * @param utilization A model of the CPU utilization of the application. + */ + public SimRuntimeWorkload(long duration, double utilization) { + if (duration < 0) { + throw new IllegalArgumentException("Duration must be positive"); + } else if (utilization <= 0.0 || utilization > 1.0) { + throw new IllegalArgumentException("Utilization must be in (0, 1]"); + } + + this.duration = duration; + this.utilization = utilization; + } + + @Override + public void onStart(SimMachineContext ctx) { + this.ctx = ctx; + + final FlowGraph graph = ctx.getGraph(); + final FlowStage stage = graph.newStage(this); + this.stage = stage; + + final List<? extends SimProcessingUnit> cpus = ctx.getCpus(); + final OutPort[] outputs = new OutPort[cpus.size()]; + this.outputs = outputs; + + for (int i = 0; i < cpus.size(); i++) { + final SimProcessingUnit cpu = cpus.get(i); + final OutPort output = stage.getOutlet("cpu" + i); + + graph.connect(output, cpu.getInput()); + outputs[i] = output; + } + + this.remainingDuration = duration; + this.lastUpdate = graph.getEngine().getClock().millis(); + } + + @Override + public void onStop(SimMachineContext ctx) { + this.ctx = null; + + final FlowStage stage = this.stage; + if (stage != null) { + this.stage = null; + this.outputs = null; + stage.close(); + } + } + + @Override + public long onUpdate(FlowStage ctx, long now) { + long lastUpdate = this.lastUpdate; + this.lastUpdate = now; + + long delta = now - lastUpdate; + long duration = this.remainingDuration - delta; + + if (duration <= 0) { + final SimMachineContext machineContext = this.ctx; + if (machineContext != null) { + machineContext.shutdown(); + } + ctx.close(); + return Long.MAX_VALUE; + } + + this.remainingDuration = duration; + + for (final OutPort output : outputs) { + float limit = (float) (output.getCapacity() * utilization); + output.push(limit); + } + + return now + duration; + } + + @Override + public String toString() { + return "SimDurationWorkload[duration=" + duration + "ms,utilization=" + utilization + "]"; + } +} diff --git a/opendc-simulator/opendc-simulator-compute/src/main/java/org/opendc/simulator/compute/workload/SimTrace.java b/opendc-simulator/opendc-simulator-compute/src/main/java/org/opendc/simulator/compute/workload/SimTrace.java new file mode 100644 index 00000000..12a567ff --- /dev/null +++ b/opendc-simulator/opendc-simulator-compute/src/main/java/org/opendc/simulator/compute/workload/SimTrace.java @@ -0,0 +1,422 @@ +/* + * Copyright (c) 2022 AtLarge Research + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ + +package org.opendc.simulator.compute.workload; + +import java.util.Arrays; +import java.util.List; +import org.opendc.simulator.compute.SimMachineContext; +import org.opendc.simulator.compute.SimProcessingUnit; +import org.opendc.simulator.flow2.FlowGraph; +import org.opendc.simulator.flow2.FlowStage; +import org.opendc.simulator.flow2.FlowStageLogic; +import org.opendc.simulator.flow2.OutPort; + +/** + * A workload trace that describes the resource utilization over time in a collection of {@link SimTraceFragment}s. + */ +public final class SimTrace { + private final double[] usageCol; + private final long[] deadlineCol; + private final int[] coresCol; + private final int size; + + /** + * Construct a {@link SimTrace} instance. + * + * @param usageCol The column containing the CPU usage of each fragment (in MHz). + * @param deadlineCol The column containing the ending timestamp for each fragment (in epoch millis). + * @param coresCol The column containing the utilized cores. + * @param size The number of fragments in the trace. + */ + private SimTrace(double[] usageCol, long[] deadlineCol, int[] coresCol, int size) { + if (size < 0) { + throw new IllegalArgumentException("Invalid trace size"); + } else if (usageCol.length < size) { + throw new IllegalArgumentException("Invalid number of usage entries"); + } else if (deadlineCol.length < size) { + throw new IllegalArgumentException("Invalid number of deadline entries"); + } else if (coresCol.length < size) { + throw new IllegalArgumentException("Invalid number of core entries"); + } + + this.usageCol = usageCol; + this.deadlineCol = deadlineCol; + this.coresCol = coresCol; + this.size = size; + } + + /** + * Construct a {@link SimWorkload} for this trace. + * + * @param offset The offset for the timestamps. + */ + public SimWorkload createWorkload(long offset) { + return new Workload(offset, usageCol, deadlineCol, coresCol, size); + } + + /** + * Create a new {@link Builder} instance with the specified initial capacity. + */ + public static Builder builder(int initialCapacity) { + return new Builder(initialCapacity); + } + + /** + * Create a new {@link Builder} instance with a default initial capacity. + */ + public static Builder builder() { + return builder(256); + } + + /** + * Construct a {@link SimTrace} from the specified fragments. + * + * @param fragments The array of fragments to construct the trace from. + */ + public static SimTrace ofFragments(SimTraceFragment... fragments) { + final Builder builder = builder(fragments.length); + + for (SimTraceFragment fragment : fragments) { + builder.add(fragment.timestamp + fragment.duration, fragment.usage, fragment.cores); + } + + return builder.build(); + } + + /** + * Construct a {@link SimTrace} from the specified fragments. + * + * @param fragments The fragments to construct the trace from. + */ + public static SimTrace ofFragments(List<SimTraceFragment> fragments) { + final Builder builder = builder(fragments.size()); + + for (SimTraceFragment fragment : fragments) { + builder.add(fragment.timestamp + fragment.duration, fragment.usage, fragment.cores); + } + + return builder.build(); + } + + /** + * Builder class for a {@link SimTrace}. + */ + public static final class Builder { + private double[] usageCol; + private long[] deadlineCol; + private int[] coresCol; + + private int size; + private boolean isBuilt; + + /** + * Construct a new {@link Builder} instance. + */ + private Builder(int initialCapacity) { + this.usageCol = new double[initialCapacity]; + this.deadlineCol = new long[initialCapacity]; + this.coresCol = new int[initialCapacity]; + } + + /** + * Add a fragment to the trace. + * + * @param deadline The timestamp at which the fragment ends (in epoch millis). + * @param usage The CPU usage at this fragment. + * @param cores The number of cores used during this fragment. + */ + public void add(long deadline, double usage, int cores) { + if (isBuilt) { + recreate(); + } + + int size = this.size; + double[] usageCol = this.usageCol; + + if (size == usageCol.length) { + grow(); + usageCol = this.usageCol; + } + + deadlineCol[size] = deadline; + usageCol[size] = usage; + coresCol[size] = cores; + + this.size++; + } + + /** + * Build the {@link SimTrace} instance. + */ + public SimTrace build() { + isBuilt = true; + return new SimTrace(usageCol, deadlineCol, coresCol, size); + } + + /** + * Helper method to grow the capacity of the trace. + */ + private void grow() { + int arraySize = usageCol.length; + int newSize = arraySize + (arraySize >> 1); + + usageCol = Arrays.copyOf(usageCol, newSize); + deadlineCol = Arrays.copyOf(deadlineCol, newSize); + coresCol = Arrays.copyOf(coresCol, newSize); + } + + /** + * Clone the columns of the trace. + * + * <p> + * This is necessary when a {@link SimTrace} has been built already, but the user is again adding entries to + * the builder. + */ + private void recreate() { + isBuilt = false; + usageCol = usageCol.clone(); + deadlineCol = deadlineCol.clone(); + coresCol = coresCol.clone(); + } + } + + /** + * Implementation of {@link SimWorkload} that executes a trace. + */ + private static class Workload implements SimWorkload { + private WorkloadStageLogic logic; + + private final long offset; + private final double[] usageCol; + private final long[] deadlineCol; + private final int[] coresCol; + private final int size; + + private Workload(long offset, double[] usageCol, long[] deadlineCol, int[] coresCol, int size) { + this.offset = offset; + this.usageCol = usageCol; + this.deadlineCol = deadlineCol; + this.coresCol = coresCol; + this.size = size; + } + + @Override + public void onStart(SimMachineContext ctx) { + final WorkloadStageLogic logic; + if (ctx.getCpus().size() == 1) { + logic = new SingleWorkloadLogic(ctx, offset, usageCol, deadlineCol, size); + } else { + logic = new MultiWorkloadLogic(ctx, offset, usageCol, deadlineCol, coresCol, size); + } + this.logic = logic; + } + + @Override + public void onStop(SimMachineContext ctx) { + final WorkloadStageLogic logic = this.logic; + + if (logic != null) { + this.logic = null; + logic.getStage().close(); + } + } + } + + /** + * Interface to represent the {@link FlowStage} that simulates the trace workload. + */ + private interface WorkloadStageLogic extends FlowStageLogic { + /** + * Return the {@link FlowStage} belonging to this instance. + */ + FlowStage getStage(); + } + + /** + * Implementation of {@link FlowStageLogic} for just a single CPU resource. + */ + private static class SingleWorkloadLogic implements WorkloadStageLogic { + private final FlowStage stage; + private final OutPort output; + private int index; + + private final long offset; + private final double[] usageCol; + private final long[] deadlineCol; + private final int size; + + private final SimMachineContext ctx; + + private SingleWorkloadLogic( + SimMachineContext ctx, long offset, double[] usageCol, long[] deadlineCol, int size) { + this.ctx = ctx; + this.offset = offset; + this.usageCol = usageCol; + this.deadlineCol = deadlineCol; + this.size = size; + + final FlowGraph graph = ctx.getGraph(); + final List<? extends SimProcessingUnit> cpus = ctx.getCpus(); + + stage = graph.newStage(this); + + final SimProcessingUnit cpu = cpus.get(0); + final OutPort output = stage.getOutlet("cpu"); + this.output = output; + + graph.connect(output, cpu.getInput()); + } + + @Override + public long onUpdate(FlowStage ctx, long now) { + int size = this.size; + long offset = this.offset; + long nowOffset = now - offset; + + int index = this.index; + + long[] deadlines = deadlineCol; + long deadline = deadlines[index]; + + while (deadline <= nowOffset) { + if (++index >= size) { + return doStop(ctx); + } + deadline = deadlines[index]; + } + + this.index = index; + this.output.push((float) usageCol[index]); + return deadline + offset; + } + + @Override + public FlowStage getStage() { + return stage; + } + + /** + * Helper method to stop the execution of the workload. + */ + private long doStop(FlowStage ctx) { + final SimMachineContext machineContext = this.ctx; + if (machineContext != null) { + machineContext.shutdown(); + } + ctx.close(); + return Long.MAX_VALUE; + } + } + + /** + * Implementation of {@link FlowStageLogic} for multiple CPUs. + */ + private static class MultiWorkloadLogic implements WorkloadStageLogic { + private final FlowStage stage; + private final OutPort[] outputs; + private int index; + private final int coreCount; + + private final long offset; + private final double[] usageCol; + private final long[] deadlineCol; + private final int[] coresCol; + private final int size; + + private final SimMachineContext ctx; + + private MultiWorkloadLogic( + SimMachineContext ctx, long offset, double[] usageCol, long[] deadlineCol, int[] coresCol, int size) { + this.ctx = ctx; + this.offset = offset; + this.usageCol = usageCol; + this.deadlineCol = deadlineCol; + this.coresCol = coresCol; + this.size = size; + + final FlowGraph graph = ctx.getGraph(); + final List<? extends SimProcessingUnit> cpus = ctx.getCpus(); + + stage = graph.newStage(this); + coreCount = cpus.size(); + + final OutPort[] outputs = new OutPort[cpus.size()]; + this.outputs = outputs; + + for (int i = 0; i < cpus.size(); i++) { + final SimProcessingUnit cpu = cpus.get(i); + final OutPort output = stage.getOutlet("cpu" + i); + + graph.connect(output, cpu.getInput()); + outputs[i] = output; + } + } + + @Override + public long onUpdate(FlowStage ctx, long now) { + int size = this.size; + long offset = this.offset; + long nowOffset = now - offset; + + int index = this.index; + + long[] deadlines = deadlineCol; + long deadline = deadlines[index]; + + while (deadline <= nowOffset && ++index < size) { + deadline = deadlines[index]; + } + + if (index >= size) { + final SimMachineContext machineContext = this.ctx; + if (machineContext != null) { + machineContext.shutdown(); + } + ctx.close(); + return Long.MAX_VALUE; + } + + this.index = index; + + int cores = Math.min(coreCount, coresCol[index]); + float usage = (float) usageCol[index] / cores; + + final OutPort[] outputs = this.outputs; + + for (int i = 0; i < cores; i++) { + outputs[i].push(usage); + } + + for (int i = cores; i < outputs.length; i++) { + outputs[i].push(0.f); + } + + return deadline + offset; + } + + @Override + public FlowStage getStage() { + return stage; + } + } +} diff --git a/opendc-simulator/opendc-simulator-compute/src/main/java/org/opendc/simulator/compute/workload/SimTraceFragment.java b/opendc-simulator/opendc-simulator-compute/src/main/java/org/opendc/simulator/compute/workload/SimTraceFragment.java new file mode 100644 index 00000000..12c1348d --- /dev/null +++ b/opendc-simulator/opendc-simulator-compute/src/main/java/org/opendc/simulator/compute/workload/SimTraceFragment.java @@ -0,0 +1,94 @@ +/* + * Copyright (c) 2022 AtLarge Research + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ + +package org.opendc.simulator.compute.workload; + +import java.util.Objects; + +/** + * A fragment of the workload trace. + */ +public final class SimTraceFragment { + final long timestamp; + final long duration; + final double usage; + final int cores; + + /** + * Construct a {@link SimTraceFragment}. + * + * @param timestamp The timestamp at which the fragment starts (in epoch millis). + * @param duration The duration of the fragment (in milliseconds). + * @param usage The CPU usage during the fragment (in MHz). + * @param cores The amount of cores utilized during the fragment. + */ + public SimTraceFragment(long timestamp, long duration, double usage, int cores) { + this.timestamp = timestamp; + this.duration = duration; + this.usage = usage; + this.cores = cores; + } + + /** + * Return the timestamp at which the fragment starts (in epoch millis). + */ + public long getTimestamp() { + return timestamp; + } + + /** + * Return the duration of the fragment (in milliseconds). + */ + public long getDuration() { + return duration; + } + + /** + * Return the CPU usage during the fragment (in MHz). + */ + public double getUsage() { + return usage; + } + + /** + * Return the amount of cores utilized during the fragment. + */ + public int getCores() { + return cores; + } + + @Override + public boolean equals(Object o) { + if (this == o) return true; + if (o == null || getClass() != o.getClass()) return false; + SimTraceFragment that = (SimTraceFragment) o; + return timestamp == that.timestamp + && duration == that.duration + && Double.compare(that.usage, usage) == 0 + && cores == that.cores; + } + + @Override + public int hashCode() { + return Objects.hash(timestamp, duration, usage, cores); + } +} diff --git a/opendc-simulator/opendc-simulator-compute/src/main/kotlin/org/opendc/simulator/compute/workload/SimWorkload.kt b/opendc-simulator/opendc-simulator-compute/src/main/java/org/opendc/simulator/compute/workload/SimWorkload.java index 61c6e2ad..7be51265 100644 --- a/opendc-simulator/opendc-simulator-compute/src/main/kotlin/org/opendc/simulator/compute/workload/SimWorkload.kt +++ b/opendc-simulator/opendc-simulator-compute/src/main/java/org/opendc/simulator/compute/workload/SimWorkload.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2020 AtLarge Research + * Copyright (c) 2022 AtLarge Research * * Permission is hereby granted, free of charge, to any person obtaining a copy * of this software and associated documentation files (the "Software"), to deal @@ -20,15 +20,16 @@ * SOFTWARE. */ -package org.opendc.simulator.compute.workload +package org.opendc.simulator.compute.workload; -import org.opendc.simulator.compute.SimMachineContext +import org.opendc.simulator.compute.SimMachineContext; /** * A model that characterizes the runtime behavior of some particular workload. * + * <p> * Workloads are stateful objects that may be paused and resumed at a later moment. As such, be careful when using the - * same [SimWorkload] from multiple contexts. + * same {@link SimWorkload} from multiple contexts. */ public interface SimWorkload { /** @@ -36,12 +37,12 @@ public interface SimWorkload { * * @param ctx The execution context in which the machine runs. */ - public fun onStart(ctx: SimMachineContext) + void onStart(SimMachineContext ctx); /** * This method is invoked when the workload is stopped. * * @param ctx The execution context in which the machine runs. */ - public fun onStop(ctx: SimMachineContext) + void onStop(SimMachineContext ctx); } diff --git a/opendc-simulator/opendc-simulator-power/src/main/kotlin/org/opendc/simulator/power/SimPowerSource.kt b/opendc-simulator/opendc-simulator-compute/src/main/java/org/opendc/simulator/compute/workload/SimWorkloadLifecycle.java index 07e9f52e..f0e2561f 100644 --- a/opendc-simulator/opendc-simulator-power/src/main/kotlin/org/opendc/simulator/power/SimPowerSource.kt +++ b/opendc-simulator/opendc-simulator-compute/src/main/java/org/opendc/simulator/compute/workload/SimWorkloadLifecycle.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2021 AtLarge Research + * Copyright (c) 2022 AtLarge Research * * Permission is hereby granted, free of charge, to any person obtaining a copy * of this software and associated documentation files (the "Software"), to deal @@ -20,35 +20,41 @@ * SOFTWARE. */ -package org.opendc.simulator.power +package org.opendc.simulator.compute.workload; -import org.opendc.simulator.flow.FlowEngine -import org.opendc.simulator.flow.FlowSink +import java.util.HashSet; +import org.opendc.simulator.compute.SimMachineContext; /** - * A [SimPowerOutlet] that represents a source of electricity. - * - * @param engine The underlying [FlowEngine] to drive the simulation under the hood. + * A helper class to manage the lifecycle of a {@link SimWorkload}. */ -public class SimPowerSource(engine: FlowEngine, public val capacity: Double) : SimPowerOutlet() { - /** - * The resource source that drives this power source. - */ - private val source = FlowSink(engine, capacity) +public final class SimWorkloadLifecycle { + private final SimMachineContext ctx; + private final HashSet<Runnable> waiting = new HashSet<>(); /** - * The power draw at this instant. + * Construct a {@link SimWorkloadLifecycle} instance. + * + * @param ctx The {@link SimMachineContext} of the workload. */ - public val powerDraw: Double - get() = source.rate - - override fun onConnect(inlet: SimPowerInlet) { - source.startConsumer(inlet.createSource()) + public SimWorkloadLifecycle(SimMachineContext ctx) { + this.ctx = ctx; } - override fun onDisconnect(inlet: SimPowerInlet) { - source.cancel() + /** + * Register a "completer" callback that must be invoked before ending the lifecycle of the workload. + */ + public Runnable newCompleter() { + Runnable completer = new Runnable() { + @Override + public void run() { + final HashSet<Runnable> waiting = SimWorkloadLifecycle.this.waiting; + if (waiting.remove(this) && waiting.isEmpty()) { + ctx.shutdown(); + } + } + }; + waiting.add(completer); + return completer; } - - override fun toString(): String = "SimPowerSource" } diff --git a/opendc-simulator/opendc-simulator-compute/src/main/kotlin/org/opendc/simulator/compute/SimAbstractMachine.kt b/opendc-simulator/opendc-simulator-compute/src/main/kotlin/org/opendc/simulator/compute/SimAbstractMachine.kt deleted file mode 100644 index 71784567..00000000 --- a/opendc-simulator/opendc-simulator-compute/src/main/kotlin/org/opendc/simulator/compute/SimAbstractMachine.kt +++ /dev/null @@ -1,239 +0,0 @@ -/* - * Copyright (c) 2021 AtLarge Research - * - * Permission is hereby granted, free of charge, to any person obtaining a copy - * of this software and associated documentation files (the "Software"), to deal - * in the Software without restriction, including without limitation the rights - * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell - * copies of the Software, and to permit persons to whom the Software is - * furnished to do so, subject to the following conditions: - * - * The above copyright notice and this permission notice shall be included in all - * copies or substantial portions of the Software. - * - * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR - * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, - * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE - * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER - * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, - * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE - * SOFTWARE. - */ - -package org.opendc.simulator.compute - -import mu.KotlinLogging -import org.opendc.simulator.compute.device.SimNetworkAdapter -import org.opendc.simulator.compute.device.SimPeripheral -import org.opendc.simulator.compute.model.MachineModel -import org.opendc.simulator.compute.model.MemoryUnit -import org.opendc.simulator.compute.model.NetworkAdapter -import org.opendc.simulator.compute.model.StorageDevice -import org.opendc.simulator.compute.workload.SimWorkload -import org.opendc.simulator.flow.FlowConsumer -import org.opendc.simulator.flow.FlowConvergenceListener -import org.opendc.simulator.flow.FlowEngine -import org.opendc.simulator.flow.FlowForwarder -import org.opendc.simulator.flow.FlowSink -import org.opendc.simulator.flow.FlowSource -import org.opendc.simulator.flow.batch - -/** - * Abstract implementation of the [SimMachine] interface. - * - * @param engine The engine to manage the machine's resources. - * @param model The model of the machine. - */ -public abstract class SimAbstractMachine( - protected val engine: FlowEngine, - final override val model: MachineModel -) : SimMachine, FlowConvergenceListener { - /** - * The resources allocated for this machine. - */ - public abstract val cpus: List<SimProcessingUnit> - - /** - * The memory interface of the machine. - */ - public val memory: SimMemory = Memory(FlowSink(engine, model.memory.sumOf { it.size }.toDouble()), model.memory) - - /** - * The network interfaces available to the machine. - */ - public val net: List<SimNetworkInterface> = model.net.mapIndexed { i, adapter -> NetworkAdapterImpl(engine, adapter, i) } - - /** - * The network interfaces available to the machine. - */ - public val storage: List<SimStorageInterface> = model.storage.mapIndexed { i, device -> StorageDeviceImpl(engine, device, i) } - - /** - * The peripherals of the machine. - */ - public override val peripherals: List<SimPeripheral> = net.map { it as SimNetworkAdapter } - - /** - * The current active [Context]. - */ - private var _ctx: Context? = null - - override fun startWorkload(workload: SimWorkload, meta: Map<String, Any>): SimMachineContext { - check(_ctx == null) { "A machine cannot run concurrently" } - - val ctx = Context(workload, meta) - ctx.start() - return ctx - } - - override fun cancel() { - _ctx?.close() - } - - override fun onConverge(now: Long) {} - - /** - * The execution context in which the workload runs. - * - * @param workload The workload that is running on the machine. - * @param meta The metadata passed to the workload. - */ - private inner class Context( - private val workload: SimWorkload, - override val meta: Map<String, Any> - ) : SimMachineContext { - /** - * A flag to indicate that the context has been closed. - */ - private var isClosed = false - - val engine: FlowEngine = this@SimAbstractMachine.engine - - /** - * Start this context. - */ - fun start() { - try { - _ctx = this - engine.batch { workload.onStart(this) } - } catch (cause: Throwable) { - logger.warn(cause) { "Workload failed during onStart callback" } - close() - } - } - - override val cpus: List<SimProcessingUnit> = this@SimAbstractMachine.cpus - - override val memory: SimMemory = this@SimAbstractMachine.memory - - override val net: List<SimNetworkInterface> = this@SimAbstractMachine.net - - override val storage: List<SimStorageInterface> = this@SimAbstractMachine.storage - - override fun close() { - if (isClosed) { - return - } - - isClosed = true - assert(_ctx == this) { "Invariant violation: multiple contexts active for a single machine" } - _ctx = null - - // Cancel all the resources associated with the machine - doCancel() - - try { - workload.onStop(this) - } catch (cause: Throwable) { - logger.warn(cause) { "Workload failed during onStop callback" } - } - } - - /** - * Run the stop procedures for the resources associated with the machine. - */ - private fun doCancel() { - engine.batch { - for (cpu in cpus) { - cpu.cancel() - } - - memory.cancel() - - for (ifx in net) { - (ifx as NetworkAdapterImpl).disconnect() - } - - for (storage in storage) { - val impl = storage as StorageDeviceImpl - impl.read.cancel() - impl.write.cancel() - } - } - } - - override fun toString(): String = "SimAbstractMachine.Context" - } - - /** - * The [SimMemory] implementation for a machine. - */ - private class Memory(source: FlowSink, override val models: List<MemoryUnit>) : SimMemory, FlowConsumer by source { - override fun toString(): String = "SimAbstractMachine.Memory" - } - - /** - * The [SimNetworkAdapter] implementation for a machine. - */ - private class NetworkAdapterImpl( - engine: FlowEngine, - model: NetworkAdapter, - index: Int - ) : SimNetworkAdapter(), SimNetworkInterface { - override val name: String = "eth$index" - - override val bandwidth: Double = model.bandwidth - - override val provider: FlowConsumer - get() = _rx - - override fun createConsumer(): FlowSource = _tx - - override val tx: FlowConsumer - get() = _tx - private val _tx = FlowForwarder(engine) - - override val rx: FlowSource - get() = _rx - private val _rx = FlowForwarder(engine) - - override fun toString(): String = "SimAbstractMachine.NetworkAdapterImpl[name=$name,bandwidth=$bandwidth]" - } - - /** - * The [SimStorageInterface] implementation for a machine. - */ - private class StorageDeviceImpl( - engine: FlowEngine, - model: StorageDevice, - index: Int - ) : SimStorageInterface { - override val name: String = "disk$index" - - override val capacity: Double = model.capacity - - override val read: FlowConsumer = FlowSink(engine, model.readBandwidth) - - override val write: FlowConsumer = FlowSink(engine, model.writeBandwidth) - - override fun toString(): String = "SimAbstractMachine.StorageDeviceImpl[name=$name,capacity=$capacity]" - } - - private companion object { - /** - * The logging instance associated with this class. - */ - @JvmStatic - private val logger = KotlinLogging.logger {} - } -} diff --git a/opendc-simulator/opendc-simulator-compute/src/main/kotlin/org/opendc/simulator/compute/SimBareMetalMachine.kt b/opendc-simulator/opendc-simulator-compute/src/main/kotlin/org/opendc/simulator/compute/SimBareMetalMachine.kt deleted file mode 100644 index 4c824440..00000000 --- a/opendc-simulator/opendc-simulator-compute/src/main/kotlin/org/opendc/simulator/compute/SimBareMetalMachine.kt +++ /dev/null @@ -1,130 +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.simulator.compute - -import org.opendc.simulator.compute.device.SimPsu -import org.opendc.simulator.compute.model.MachineModel -import org.opendc.simulator.compute.model.ProcessingUnit -import org.opendc.simulator.compute.power.PowerDriver -import org.opendc.simulator.flow.FlowConsumer -import org.opendc.simulator.flow.FlowEngine -import org.opendc.simulator.flow.FlowSink -import kotlin.math.max - -/** - * A simulated bare-metal machine that is able to run a single workload. - * - * A [SimBareMetalMachine] is a stateful object, and you should be careful when operating this object concurrently. For - * example, the class expects only a single concurrent call to [run]. - * - * @param engine The [FlowEngine] to drive the simulation. - * @param model The machine model to simulate. - * @param powerDriver The power driver to use. - * @param psu The power supply of the machine. - */ -public class SimBareMetalMachine( - engine: FlowEngine, - model: MachineModel, - powerDriver: PowerDriver, - public val psu: SimPsu = SimPsu(500.0, mapOf(1.0 to 1.0)) -) : SimAbstractMachine(engine, model) { - /** - * The current power usage of the machine (without PSU loss) in W. - */ - public val powerUsage: Double - get() = _powerUsage - private var _powerUsage = 0.0 - - /** - * The total energy usage of the machine (without PSU loss) in Joules. - */ - public val energyUsage: Double - get() { - computeEnergyUsage(engine.clock.millis()) - return _energyUsage - } - private var _energyUsage = 0.0 - private var _energyLastComputation = 0L - - /** - * The processing units of the machine. - */ - override val cpus: List<SimProcessingUnit> = model.cpus.map { cpu -> - Cpu(FlowSink(engine, cpu.frequency, this@SimBareMetalMachine), cpu) - } - - /** - * The logic of the power driver. - */ - private val powerDriverLogic = powerDriver.createLogic(this, cpus) - - private var _lastConverge = Long.MAX_VALUE - - override fun onConverge(now: Long) { - // Update the PSU stage - psu.update() - - val lastConverge = _lastConverge - _lastConverge = now - val duration = max(0, now - lastConverge) - if (duration > 0) { - // Compute the power and energy usage of the machine - computeEnergyUsage(now) - } - - _powerUsage = powerDriverLogic.computePower() - } - - init { - psu.connect(powerDriverLogic) - _powerUsage = powerDriverLogic.computePower() - } - - /** - * Helper method to compute total energy usage. - */ - private fun computeEnergyUsage(now: Long) { - val duration = max(0, now - _energyLastComputation) - _energyLastComputation = now - - // Compute the energy usage of the machine - _energyUsage += _powerUsage * (duration / 1000.0) - } - - /** - * A [SimProcessingUnit] of a bare-metal machine. - */ - private class Cpu( - private val source: FlowSink, - override val model: ProcessingUnit - ) : SimProcessingUnit, FlowConsumer by source { - override var capacity: Double - get() = source.capacity - set(value) { - // Clamp the capacity of the CPU between [0.0, maxFreq] - source.capacity = value.coerceIn(0.0, model.frequency) - } - - override fun toString(): String = "SimBareMetalMachine.Cpu[model=$model]" - } -} diff --git a/opendc-simulator/opendc-simulator-compute/src/main/kotlin/org/opendc/simulator/compute/device/SimPsu.kt b/opendc-simulator/opendc-simulator-compute/src/main/kotlin/org/opendc/simulator/compute/device/SimPsu.kt deleted file mode 100644 index 3d3703ae..00000000 --- a/opendc-simulator/opendc-simulator-compute/src/main/kotlin/org/opendc/simulator/compute/device/SimPsu.kt +++ /dev/null @@ -1,114 +0,0 @@ -/* - * Copyright (c) 2021 AtLarge Research - * - * Permission is hereby granted, free of charge, to any person obtaining a copy - * of this software and associated documentation files (the "Software"), to deal - * in the Software without restriction, including without limitation the rights - * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell - * copies of the Software, and to permit persons to whom the Software is - * furnished to do so, subject to the following conditions: - * - * The above copyright notice and this permission notice shall be included in all - * copies or substantial portions of the Software. - * - * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR - * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, - * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE - * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER - * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, - * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE - * SOFTWARE. - */ - -package org.opendc.simulator.compute.device - -import org.opendc.simulator.compute.power.PowerDriver -import org.opendc.simulator.flow.FlowConnection -import org.opendc.simulator.flow.FlowSource -import org.opendc.simulator.power.SimPowerInlet -import java.util.TreeMap - -/** - * A power supply of a [SimBareMetalMachine]. - * - * @param ratedOutputPower The rated output power of the PSU. - * @param energyEfficiency The energy efficiency of the PSU for various power draws. - */ -public class SimPsu( - private val ratedOutputPower: Double, - energyEfficiency: Map<Double, Double> -) : SimPowerInlet() { - /** - * The power draw of the machine at this instant. - */ - public val powerDraw: Double - get() = _powerDraw - private var _powerDraw = 0.0 - - /** - * The energy efficiency of the PSU at various power draws. - */ - private val energyEfficiency = TreeMap(energyEfficiency) - - /** - * The consumer context. - */ - private var _ctx: FlowConnection? = null - - /** - * The driver that is connected to the PSU. - */ - private var _driver: PowerDriver.Logic? = null - - init { - require(energyEfficiency.isNotEmpty()) { "Must specify at least one entry for energy efficiency of PSU" } - } - - /** - * Update the power draw of the PSU. - */ - public fun update() { - _ctx?.pull() - } - - /** - * Connect the specified [PowerDriver.Logic] to this PSU. - */ - public fun connect(driver: PowerDriver.Logic) { - check(_driver == null) { "PSU already connected" } - _driver = driver - update() - } - - override fun createSource(): FlowSource = object : FlowSource { - override fun onStart(conn: FlowConnection, now: Long) { - _ctx = conn - conn.shouldSourceConverge = true - } - - override fun onStop(conn: FlowConnection, now: Long) { - _ctx = null - } - - override fun onPull(conn: FlowConnection, now: Long): Long { - val powerDraw = computePowerDraw(_driver?.computePower() ?: 0.0) - conn.push(powerDraw) - return Long.MAX_VALUE - } - - override fun onConverge(conn: FlowConnection, now: Long) { - _powerDraw = conn.rate - } - } - - /** - * Compute the power draw of the PSU including the power loss. - */ - private fun computePowerDraw(load: Double): Double { - val loadPercentage = (load / ratedOutputPower).coerceIn(0.0, 1.0) - val efficiency = energyEfficiency.ceilingEntry(loadPercentage)?.value ?: 1.0 - return load / efficiency - } - - override fun toString(): String = "SimPsu[draw=$_powerDraw]" -} diff --git a/opendc-simulator/opendc-simulator-compute/src/main/kotlin/org/opendc/simulator/compute/kernel/SimHypervisor.kt b/opendc-simulator/opendc-simulator-compute/src/main/kotlin/org/opendc/simulator/compute/kernel/SimHypervisor.kt deleted file mode 100644 index e1486d71..00000000 --- a/opendc-simulator/opendc-simulator-compute/src/main/kotlin/org/opendc/simulator/compute/kernel/SimHypervisor.kt +++ /dev/null @@ -1,442 +0,0 @@ -/* - * Copyright (c) 2021 AtLarge Research - * - * Permission is hereby granted, free of charge, to any person obtaining a copy - * of this software and associated documentation files (the "Software"), to deal - * in the Software without restriction, including without limitation the rights - * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell - * copies of the Software, and to permit persons to whom the Software is - * furnished to do so, subject to the following conditions: - * - * The above copyright notice and this permission notice shall be included in all - * copies or substantial portions of the Software. - * - * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR - * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, - * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE - * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER - * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, - * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE - * SOFTWARE. - */ - -package org.opendc.simulator.compute.kernel - -import org.opendc.simulator.compute.SimAbstractMachine -import org.opendc.simulator.compute.SimMachine -import org.opendc.simulator.compute.SimMachineContext -import org.opendc.simulator.compute.SimProcessingUnit -import org.opendc.simulator.compute.kernel.cpufreq.ScalingGovernor -import org.opendc.simulator.compute.kernel.cpufreq.ScalingPolicy -import org.opendc.simulator.compute.kernel.interference.VmInterferenceDomain -import org.opendc.simulator.compute.kernel.interference.VmInterferenceMember -import org.opendc.simulator.compute.kernel.interference.VmInterferenceProfile -import org.opendc.simulator.compute.model.MachineModel -import org.opendc.simulator.compute.model.ProcessingUnit -import org.opendc.simulator.compute.workload.SimWorkload -import org.opendc.simulator.flow.FlowConsumer -import org.opendc.simulator.flow.FlowConvergenceListener -import org.opendc.simulator.flow.FlowEngine -import org.opendc.simulator.flow.mux.FlowMultiplexer -import org.opendc.simulator.flow.mux.FlowMultiplexerFactory -import java.util.SplittableRandom -import kotlin.math.roundToLong - -/** - * A SimHypervisor facilitates the execution of multiple concurrent [SimWorkload]s, while acting as a single workload - * to another [SimMachine]. - * - * @param engine The [FlowEngine] to drive the simulation. - * @param muxFactory The factor for the [FlowMultiplexer] to multiplex the workloads. - * @param random A randomness generator for the interference calculations. - * @param scalingGovernor The scaling governor to use for scaling the CPU frequency of the underlying hardware. - * @param interferenceDomain The interference domain to which the hypervisor belongs. - */ -public class SimHypervisor( - private val engine: FlowEngine, - muxFactory: FlowMultiplexerFactory, - private val random: SplittableRandom, - private val scalingGovernor: ScalingGovernor? = null, - private val interferenceDomain: VmInterferenceDomain = VmInterferenceDomain() -) : SimWorkload, FlowConvergenceListener { - /** - * The [FlowMultiplexer] to multiplex the virtual machines. - */ - private val mux = muxFactory.newMultiplexer(engine, this) - - /** - * The virtual machines running on this hypervisor. - */ - private val _vms = mutableSetOf<VirtualMachine>() - public val vms: Set<SimMachine> - get() = _vms - - /** - * The resource counters associated with the hypervisor. - */ - public val counters: SimHypervisorCounters - get() = _counters - private val _counters = CountersImpl(this) - - /** - * The CPU capacity of the hypervisor in MHz. - */ - public val cpuCapacity: Double - get() = mux.capacity - - /** - * The CPU demand of the hypervisor in MHz. - */ - public val cpuDemand: Double - get() = mux.demand - - /** - * The CPU usage of the hypervisor in MHz. - */ - public val cpuUsage: Double - get() = mux.rate - - /** - * The machine on which the hypervisor runs. - */ - private lateinit var context: SimMachineContext - - /** - * The scaling governors attached to the physical CPUs backing this hypervisor. - */ - private val governors = mutableListOf<ScalingGovernor.Logic>() - - /* SimHypervisor */ - /** - * Create a [SimMachine] instance on which users may run a [SimWorkload]. - * - * @param model The machine to create. - */ - public fun newMachine(model: MachineModel): SimVirtualMachine { - require(canFit(model)) { "Machine does not fit" } - val vm = VirtualMachine(model) - _vms.add(vm) - return vm - } - - /** - * Remove the specified [machine] from the hypervisor. - * - * @param machine The machine to remove. - */ - public fun removeMachine(machine: SimVirtualMachine) { - if (_vms.remove(machine)) { - // This cast must always succeed, since `_vms` only contains `VirtualMachine` types. - (machine as VirtualMachine).close() - } - } - - /** - * Determine whether the specified machine characterized by [model] can fit on this hypervisor at this moment. - */ - public fun canFit(model: MachineModel): Boolean { - return (mux.maxInputs - mux.inputs.size) >= model.cpus.size - } - - /* SimWorkload */ - override fun onStart(ctx: SimMachineContext) { - context = ctx - - _cpuCount = ctx.cpus.size - _cpuCapacity = ctx.cpus.sumOf { it.model.frequency } - _counters.d = _cpuCount / _cpuCapacity * 1000L - - // Clear the existing outputs of the multiplexer - mux.clearOutputs() - - for (cpu in ctx.cpus) { - val governor = scalingGovernor?.createLogic(ScalingPolicyImpl(cpu)) - if (governor != null) { - governors.add(governor) - governor.onStart() - } - - cpu.startConsumer(mux.newOutput()) - } - } - - override fun onStop(ctx: SimMachineContext) {} - - private var _cpuCount = 0 - private var _cpuCapacity = 0.0 - private var _lastConverge = engine.clock.millis() - - /* FlowConvergenceListener */ - override fun onConverge(now: Long) { - val lastConverge = _lastConverge - _lastConverge = now - val delta = now - lastConverge - - if (delta > 0) { - _counters.record() - - val mux = mux - val load = mux.rate / mux.capacity.coerceAtLeast(1.0) - val random = random - - for (vm in _vms) { - vm._counters.record(random, load) - } - } - - val load = cpuDemand / cpuCapacity - for (governor in governors) { - governor.onLimit(load) - } - } - - /** - * A virtual machine running on the hypervisor. - * - * @param model The machine model of the virtual machine. - */ - private inner class VirtualMachine(model: MachineModel) : SimAbstractMachine(engine, model), SimVirtualMachine, AutoCloseable { - /** - * A flag to indicate that the machine is closed. - */ - private var isClosed = false - - /** - * The vCPUs of the machine. - */ - override val cpus = model.cpus.map { cpu -> VCpu(mux, mux.newInput(cpu.frequency), cpu) } - - /** - * The resource counters associated with the hypervisor. - */ - override val counters: SimHypervisorCounters - get() = _counters - - @JvmField val _counters = VmCountersImpl(cpus, null) - - /** - * The CPU capacity of the hypervisor in MHz. - */ - override val cpuCapacity: Double - get() = cpus.sumOf(FlowConsumer::capacity) - - /** - * The CPU demand of the hypervisor in MHz. - */ - override val cpuDemand: Double - get() = cpus.sumOf(FlowConsumer::demand) - - /** - * The CPU usage of the hypervisor in MHz. - */ - override val cpuUsage: Double - get() = cpus.sumOf(FlowConsumer::rate) - - override fun startWorkload(workload: SimWorkload, meta: Map<String, Any>): SimMachineContext { - check(!isClosed) { "Machine is closed" } - - val profile = meta["interference-profile"] as? VmInterferenceProfile - val interferenceMember = if (profile != null) interferenceDomain.join(profile) else null - - val counters = _counters - counters.member = interferenceMember - - return super.startWorkload( - object : SimWorkload { - override fun onStart(ctx: SimMachineContext) { - try { - interferenceMember?.activate() - workload.onStart(ctx) - } catch (cause: Throwable) { - interferenceMember?.deactivate() - throw cause - } - } - - override fun onStop(ctx: SimMachineContext) { - interferenceMember?.deactivate() - counters.member = null - workload.onStop(ctx) - } - }, - meta - ) - } - - override fun close() { - if (isClosed) { - return - } - - isClosed = true - cancel() - - for (cpu in cpus) { - cpu.close() - } - } - } - - /** - * A [SimProcessingUnit] of a virtual machine. - */ - private class VCpu( - private val switch: FlowMultiplexer, - private val source: FlowConsumer, - override val model: ProcessingUnit - ) : SimProcessingUnit, FlowConsumer by source { - override var capacity: Double - get() = source.capacity - set(_) = TODO("Capacity changes on vCPU not supported") - - override fun toString(): String = "SimAbstractHypervisor.VCpu[model=$model]" - - /** - * Close the CPU - */ - fun close() { - switch.removeInput(source) - } - - fun flush() { - switch.flushCounters(source) - } - } - - /** - * A [ScalingPolicy] for a physical CPU of the hypervisor. - */ - private class ScalingPolicyImpl(override val cpu: SimProcessingUnit) : ScalingPolicy { - override var target: Double - get() = cpu.capacity - set(value) { - cpu.capacity = value - } - - override val max: Double = cpu.model.frequency - - override val min: Double = 0.0 - } - - /** - * Implementation of [SimHypervisorCounters]. - */ - private class CountersImpl(private val hv: SimHypervisor) : SimHypervisorCounters { - @JvmField var d = 1.0 // Number of CPUs divided by total CPU capacity - - override val cpuActiveTime: Long - get() = _cpuTime[0] - override val cpuIdleTime: Long - get() = _cpuTime[1] - override val cpuStealTime: Long - get() = _cpuTime[2] - override val cpuLostTime: Long - get() = _cpuTime[3] - - val _cpuTime = LongArray(4) - private val _previous = DoubleArray(3) - - /** - * Record the CPU time of the hypervisor. - */ - fun record() { - val cpuTime = _cpuTime - val previous = _previous - val counters = hv.mux.counters - - val demand = counters.demand - val actual = counters.actual - val remaining = counters.remaining - - val demandDelta = demand - previous[0] - val actualDelta = actual - previous[1] - val remainingDelta = remaining - previous[2] - - previous[0] = demand - previous[1] = actual - previous[2] = remaining - - cpuTime[0] += (actualDelta * d).roundToLong() - cpuTime[1] += (remainingDelta * d).roundToLong() - cpuTime[2] += ((demandDelta - actualDelta) * d).roundToLong() - } - - override fun flush() { - hv.mux.flushCounters() - record() - } - } - - /** - * A [SimHypervisorCounters] implementation for a virtual machine. - */ - private inner class VmCountersImpl( - private val cpus: List<VCpu>, - @JvmField var member: VmInterferenceMember? - ) : SimHypervisorCounters { - private val d = cpus.size / cpus.sumOf { it.model.frequency } * 1000 - - override val cpuActiveTime: Long - get() = _cpuTime[0] - override val cpuIdleTime: Long - get() = _cpuTime[1] - override val cpuStealTime: Long - get() = _cpuTime[2] - override val cpuLostTime: Long - get() = _cpuTime[3] - - private val _cpuTime = LongArray(4) - private val _previous = DoubleArray(3) - - /** - * Record the CPU time of the hypervisor. - */ - fun record(random: SplittableRandom, load: Double) { - val cpuTime = _cpuTime - val previous = _previous - - var demand = 0.0 - var actual = 0.0 - var remaining = 0.0 - - for (cpu in cpus) { - val counters = cpu.counters - - actual += counters.actual - demand += counters.demand - remaining += counters.remaining - } - - val demandDelta = demand - previous[0] - val actualDelta = actual - previous[1] - val remainingDelta = remaining - previous[2] - - previous[0] = demand - previous[1] = actual - previous[2] = remaining - - val d = d - cpuTime[0] += (actualDelta * d).roundToLong() - cpuTime[1] += (remainingDelta * d).roundToLong() - cpuTime[2] += ((demandDelta - actualDelta) * d).roundToLong() - - // Compute the performance penalty due to flow interference - val member = member - if (member != null) { - val penalty = 1 - member.apply(random, load) - val interference = (actualDelta * d * penalty).roundToLong() - - if (interference > 0) { - cpuTime[3] += interference - _counters._cpuTime[3] += interference - } - } - } - - override fun flush() { - for (cpu in cpus) { - cpu.flush() - } - } - } -} diff --git a/opendc-simulator/opendc-simulator-compute/src/main/kotlin/org/opendc/simulator/compute/kernel/cpufreq/ConservativeScalingGovernor.kt b/opendc-simulator/opendc-simulator-compute/src/main/kotlin/org/opendc/simulator/compute/kernel/cpufreq/ConservativeScalingGovernor.kt deleted file mode 100644 index 1a03221d..00000000 --- a/opendc-simulator/opendc-simulator-compute/src/main/kotlin/org/opendc/simulator/compute/kernel/cpufreq/ConservativeScalingGovernor.kt +++ /dev/null @@ -1,66 +0,0 @@ -/* - * Copyright (c) 2021 AtLarge Research - * - * Permission is hereby granted, free of charge, to any person obtaining a copy - * of this software and associated documentation files (the "Software"), to deal - * in the Software without restriction, including without limitation the rights - * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell - * copies of the Software, and to permit persons to whom the Software is - * furnished to do so, subject to the following conditions: - * - * The above copyright notice and this permission notice shall be included in all - * copies or substantial portions of the Software. - * - * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR - * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, - * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE - * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER - * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, - * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE - * SOFTWARE. - */ - -package org.opendc.simulator.compute.kernel.cpufreq - -/** - * A CPUFreq [ScalingGovernor] that models the conservative scaling governor in the Linux kernel. - */ -public class ConservativeScalingGovernor(public val threshold: Double = 0.8, private val stepSize: Double = -1.0) : - ScalingGovernor { - override fun createLogic(policy: ScalingPolicy): ScalingGovernor.Logic = object : ScalingGovernor.Logic { - /** - * The step size to use. - */ - private val stepSize = if (this@ConservativeScalingGovernor.stepSize < 0) { - // https://github.com/torvalds/linux/blob/master/drivers/cpufreq/cpufreq_conservative.c#L33 - policy.max * 0.05 - } else { - this@ConservativeScalingGovernor.stepSize.coerceAtMost(policy.max) - } - - /** - * The previous load of the CPU. - */ - private var previousLoad = threshold - - override fun onStart() { - policy.target = policy.min - } - - override fun onLimit(load: Double) { - val currentTarget = policy.target - if (load > threshold) { - // Check for load increase (see: https://github.com/torvalds/linux/blob/master/drivers/cpufreq/cpufreq_conservative.c#L102) - val step = when { - load > previousLoad -> stepSize - load < previousLoad -> -stepSize - else -> 0.0 - } - policy.target = (currentTarget + step).coerceIn(policy.min, policy.max) - } - previousLoad = load - } - } - - override fun toString(): String = "ConservativeScalingGovernor[threshold=$threshold,stepSize=$stepSize]" -} diff --git a/opendc-simulator/opendc-simulator-compute/src/main/kotlin/org/opendc/simulator/compute/kernel/cpufreq/OnDemandScalingGovernor.kt b/opendc-simulator/opendc-simulator-compute/src/main/kotlin/org/opendc/simulator/compute/kernel/cpufreq/OnDemandScalingGovernor.kt deleted file mode 100644 index aef15ce9..00000000 --- a/opendc-simulator/opendc-simulator-compute/src/main/kotlin/org/opendc/simulator/compute/kernel/cpufreq/OnDemandScalingGovernor.kt +++ /dev/null @@ -1,50 +0,0 @@ -/* - * Copyright (c) 2021 AtLarge Research - * - * Permission is hereby granted, free of charge, to any person obtaining a copy - * of this software and associated documentation files (the "Software"), to deal - * in the Software without restriction, including without limitation the rights - * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell - * copies of the Software, and to permit persons to whom the Software is - * furnished to do so, subject to the following conditions: - * - * The above copyright notice and this permission notice shall be included in all - * copies or substantial portions of the Software. - * - * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR - * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, - * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE - * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER - * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, - * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE - * SOFTWARE. - */ - -package org.opendc.simulator.compute.kernel.cpufreq - -/** - * A CPUFreq [ScalingGovernor] that models the on-demand scaling governor in the Linux kernel. - */ -public class OnDemandScalingGovernor(public val threshold: Double = 0.8) : ScalingGovernor { - override fun createLogic(policy: ScalingPolicy): ScalingGovernor.Logic = object : ScalingGovernor.Logic { - /** - * The multiplier used for the linear frequency scaling. - */ - private val multiplier = (policy.max - policy.min) / 100 - - override fun onStart() { - policy.target = policy.min - } - - override fun onLimit(load: Double) { - policy.target = if (load < threshold) { - /* Proportional scaling (see: https://github.com/torvalds/linux/blob/master/drivers/cpufreq/cpufreq_ondemand.c#L151). */ - policy.min + load * multiplier - } else { - policy.max - } - } - } - - override fun toString(): String = "OnDemandScalingGovernor[threshold=$threshold]" -} diff --git a/opendc-simulator/opendc-simulator-compute/src/main/kotlin/org/opendc/simulator/compute/kernel/cpufreq/PerformanceScalingGovernor.kt b/opendc-simulator/opendc-simulator-compute/src/main/kotlin/org/opendc/simulator/compute/kernel/cpufreq/PerformanceScalingGovernor.kt deleted file mode 100644 index 13109a9a..00000000 --- a/opendc-simulator/opendc-simulator-compute/src/main/kotlin/org/opendc/simulator/compute/kernel/cpufreq/PerformanceScalingGovernor.kt +++ /dev/null @@ -1,36 +0,0 @@ -/* - * Copyright (c) 2021 AtLarge Research - * - * Permission is hereby granted, free of charge, to any person obtaining a copy - * of this software and associated documentation files (the "Software"), to deal - * in the Software without restriction, including without limitation the rights - * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell - * copies of the Software, and to permit persons to whom the Software is - * furnished to do so, subject to the following conditions: - * - * The above copyright notice and this permission notice shall be included in all - * copies or substantial portions of the Software. - * - * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR - * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, - * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE - * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER - * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, - * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE - * SOFTWARE. - */ - -package org.opendc.simulator.compute.kernel.cpufreq - -/** - * A CPUFreq [ScalingGovernor] that causes the highest possible frequency to be requested from the resource. - */ -public class PerformanceScalingGovernor : ScalingGovernor { - override fun createLogic(policy: ScalingPolicy): ScalingGovernor.Logic = object : ScalingGovernor.Logic { - override fun onStart() { - policy.target = policy.max - } - } - - override fun toString(): String = "PerformanceScalingGovernor" -} diff --git a/opendc-simulator/opendc-simulator-compute/src/main/kotlin/org/opendc/simulator/compute/kernel/interference/VmInterferenceDomain.kt b/opendc-simulator/opendc-simulator-compute/src/main/kotlin/org/opendc/simulator/compute/kernel/interference/VmInterferenceDomain.kt deleted file mode 100644 index 6861823b..00000000 --- a/opendc-simulator/opendc-simulator-compute/src/main/kotlin/org/opendc/simulator/compute/kernel/interference/VmInterferenceDomain.kt +++ /dev/null @@ -1,131 +0,0 @@ -/* - * Copyright (c) 2022 AtLarge Research - * - * Permission is hereby granted, free of charge, to any person obtaining a copy - * of this software and associated documentation files (the "Software"), to deal - * in the Software without restriction, including without limitation the rights - * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell - * copies of the Software, and to permit persons to whom the Software is - * furnished to do so, subject to the following conditions: - * - * The above copyright notice and this permission notice shall be included in all - * copies or substantial portions of the Software. - * - * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR - * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, - * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE - * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER - * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, - * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE - * SOFTWARE. - */ - -package org.opendc.simulator.compute.kernel.interference - -import java.util.ArrayDeque -import java.util.ArrayList -import java.util.WeakHashMap - -/** - * A domain where virtual machines may incur performance variability due to operating on the same resource and - * therefore causing interference. - */ -public class VmInterferenceDomain { - /** - * A cache to maintain a mapping between the active profiles in this domain. - */ - private val cache = WeakHashMap<VmInterferenceProfile, VmInterferenceMember>() - - /** - * The set of members active in this domain. - */ - private val activeKeys = ArrayList<VmInterferenceMember>() - - /** - * Queue of participants that will be removed or added to the active groups. - */ - private val participants = ArrayDeque<VmInterferenceMember>() - - /** - * Join this interference domain with the specified [profile] and return the [VmInterferenceMember] associated with - * the profile. If the member does not exist, it will be created. - */ - public fun join(profile: VmInterferenceProfile): VmInterferenceMember { - return cache.computeIfAbsent(profile) { key -> key.newMember(this) } - } - - /** - * Mark the specified [member] as active in this interference domain. - */ - internal fun activate(member: VmInterferenceMember) { - val activeKeys = activeKeys - val pos = activeKeys.binarySearch(member) - if (pos < 0) { - activeKeys.add(-pos - 1, member) - } - - computeActiveGroups(activeKeys, member) - } - - /** - * Mark the specified [member] as inactive in this interference domain. - */ - internal fun deactivate(member: VmInterferenceMember) { - val activeKeys = activeKeys - activeKeys.remove(member) - computeActiveGroups(activeKeys, member) - } - - /** - * (Re-)compute the active groups. - */ - private fun computeActiveGroups(activeKeys: ArrayList<VmInterferenceMember>, member: VmInterferenceMember) { - if (activeKeys.isEmpty()) { - return - } - - val groups = member.membership - val members = member.members - val participants = participants - - for (group in groups) { - val groupMembers = members[group] - - var i = 0 - var j = 0 - var intersection = 0 - - // Compute the intersection of the group members and the current active members - while (i < groupMembers.size && j < activeKeys.size) { - val l = groupMembers[i] - val rightEntry = activeKeys[j] - val r = rightEntry.id - - if (l < r) { - i++ - } else if (l > r) { - j++ - } else { - if (++intersection > 1) { - rightEntry.addGroup(group) - } else { - participants.add(rightEntry) - } - - i++ - j++ - } - } - - while (true) { - val participant = participants.poll() ?: break - - if (intersection <= 1) { - participant.removeGroup(group) - } else { - participant.addGroup(group) - } - } - } - } -} diff --git a/opendc-simulator/opendc-simulator-compute/src/main/kotlin/org/opendc/simulator/compute/kernel/interference/VmInterferenceMember.kt b/opendc-simulator/opendc-simulator-compute/src/main/kotlin/org/opendc/simulator/compute/kernel/interference/VmInterferenceMember.kt deleted file mode 100644 index 4b56a058..00000000 --- a/opendc-simulator/opendc-simulator-compute/src/main/kotlin/org/opendc/simulator/compute/kernel/interference/VmInterferenceMember.kt +++ /dev/null @@ -1,163 +0,0 @@ -/* - * Copyright (c) 2022 AtLarge Research - * - * Permission is hereby granted, free of charge, to any person obtaining a copy - * of this software and associated documentation files (the "Software"), to deal - * in the Software without restriction, including without limitation the rights - * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell - * copies of the Software, and to permit persons to whom the Software is - * furnished to do so, subject to the following conditions: - * - * The above copyright notice and this permission notice shall be included in all - * copies or substantial portions of the Software. - * - * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR - * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, - * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE - * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER - * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, - * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE - * SOFTWARE. - */ - -package org.opendc.simulator.compute.kernel.interference - -import java.util.SplittableRandom - -/** - * A participant of an interference domain. - */ -public class VmInterferenceMember( - private val domain: VmInterferenceDomain, - private val model: VmInterferenceModel, - @JvmField internal val id: Int, - @JvmField internal val membership: IntArray, - @JvmField internal val members: Array<IntArray>, - private val targets: DoubleArray, - private val scores: DoubleArray -) : Comparable<VmInterferenceMember> { - /** - * The active groups to which the key belongs. - */ - private var groups: IntArray = IntArray(2) - private var groupsSize: Int = 0 - - /** - * The number of users of the interference key. - */ - private var refCount: Int = 0 - - /** - * Mark this member as active in this interference domain. - */ - public fun activate() { - if (refCount++ <= 0) { - domain.activate(this) - } - } - - /** - * Mark this member as inactive in this interference domain. - */ - public fun deactivate() { - if (--refCount <= 0) { - domain.deactivate(this) - } - } - - /** - * Compute the performance score of the member in this interference domain. - * - * @param random The source of randomness to apply when computing the performance score. - * @param load The overall load on the interference domain. - * @return A score representing the performance score to be applied to the member, with 1 - * meaning no influence, <1 means that performance degrades, and >1 means that performance improves. - */ - public fun apply(random: SplittableRandom, load: Double): Double { - val groupsSize = groupsSize - - if (groupsSize == 0) { - return 1.0 - } - - val groups = groups - val targets = targets - - var low = 0 - var high = groupsSize - 1 - var group = -1 - - // Perform binary search over the groups based on target load - while (low <= high) { - val mid = low + high ushr 1 - val midGroup = groups[mid] - val target = targets[midGroup] - - if (target < load) { - low = mid + 1 - group = midGroup - } else if (target > load) { - high = mid - 1 - } else { - group = midGroup - break - } - } - - return if (group >= 0 && random.nextInt(members[group].size) == 0) { - scores[group] - } else { - 1.0 - } - } - - /** - * Add an active group to this member. - */ - internal fun addGroup(group: Int) { - var groups = groups - val groupsSize = groupsSize - val pos = groups.binarySearch(group, toIndex = groupsSize) - - if (pos >= 0) { - return - } - - val idx = -pos - 1 - - if (groups.size == groupsSize) { - val newSize = groupsSize + (groupsSize shr 1) - groups = groups.copyOf(newSize) - this.groups = groups - } - - groups.copyInto(groups, idx + 1, idx, groupsSize) - groups[idx] = group - this.groupsSize += 1 - } - - /** - * Remove an active group from this member. - */ - internal fun removeGroup(group: Int) { - val groups = groups - val groupsSize = groupsSize - val pos = groups.binarySearch(group, toIndex = groupsSize) - - if (pos < 0) { - return - } - - groups.copyInto(groups, pos, pos + 1, groupsSize) - this.groupsSize -= 1 - } - - override fun compareTo(other: VmInterferenceMember): Int { - val cmp = model.hashCode().compareTo(other.model.hashCode()) - if (cmp != 0) { - return cmp - } - - return id.compareTo(other.id) - } -} diff --git a/opendc-simulator/opendc-simulator-compute/src/main/kotlin/org/opendc/simulator/compute/kernel/interference/VmInterferenceModel.kt b/opendc-simulator/opendc-simulator-compute/src/main/kotlin/org/opendc/simulator/compute/kernel/interference/VmInterferenceModel.kt deleted file mode 100644 index 238bffc0..00000000 --- a/opendc-simulator/opendc-simulator-compute/src/main/kotlin/org/opendc/simulator/compute/kernel/interference/VmInterferenceModel.kt +++ /dev/null @@ -1,191 +0,0 @@ -/* - * Copyright (c) 2021 AtLarge Research - * - * Permission is hereby granted, free of charge, to any person obtaining a copy - * of this software and associated documentation files (the "Software"), to deal - * in the Software without restriction, including without limitation the rights - * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell - * copies of the Software, and to permit persons to whom the Software is - * furnished to do so, subject to the following conditions: - * - * The above copyright notice and this permission notice shall be included in all - * copies or substantial portions of the Software. - * - * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR - * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, - * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE - * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER - * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, - * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE - * SOFTWARE. - */ - -package org.opendc.simulator.compute.kernel.interference - -import java.util.TreeMap -import java.util.TreeSet - -/** - * An interference model that models the resource interference between virtual machines on a host. - * - * @param members The target load of each group. - * @param scores The performance score of each group. - * @param members The members belonging to each group. - * @param membership The identifier of each key. - * @param size The number of groups. - */ -public class VmInterferenceModel private constructor( - private val idMapping: Map<String, Int>, - private val members: Array<IntArray>, - private val membership: Array<IntArray>, - private val targets: DoubleArray, - private val scores: DoubleArray, - private val size: Int -) { - /** - * Return the [VmInterferenceProfile] associated with the specified [id]. - * - * @param id The identifier of the virtual machine. - * @return A [VmInterferenceProfile] representing the virtual machine as part of interference model or `null` if - * there is no profile for the virtual machine. - */ - public fun getProfile(id: String): VmInterferenceProfile? { - val intId = idMapping[id] ?: return null - return VmInterferenceProfile(this, intId, membership[intId], members, targets, scores) - } - - public companion object { - /** - * Construct a [Builder] instance. - */ - @JvmStatic - public fun builder(): Builder = Builder() - } - - /** - * Builder class for a [VmInterferenceModel] - */ - public class Builder internal constructor() { - /** - * The target load of each group. - */ - private var _targets = DoubleArray(INITIAL_CAPACITY) { Double.POSITIVE_INFINITY } - - /** - * The performance score of each group. - */ - private var _scores = DoubleArray(INITIAL_CAPACITY) { Double.POSITIVE_INFINITY } - - /** - * The members of each group. - */ - private var _members = ArrayList<Set<String>>(INITIAL_CAPACITY) - - /** - * The mapping from member to group id. - */ - private val ids = TreeSet<String>() - - /** - * The number of groups in the model. - */ - private var size = 0 - - /** - * Add the specified group to the model. - */ - public fun addGroup(members: Set<String>, targetLoad: Double, score: Double): Builder { - val size = size - - if (size == _targets.size) { - grow() - } - - _targets[size] = targetLoad - _scores[size] = score - _members.add(members) - ids.addAll(members) - - this.size++ - - return this - } - - /** - * Build the [VmInterferenceModel]. - */ - public fun build(): VmInterferenceModel { - val size = size - val targets = _targets - val scores = _scores - val members = _members - - val indices = IntArray(size) { it } - indices.sortedWith( - Comparator { l, r -> - var cmp = targets[l].compareTo(targets[r]) // Order by target load - if (cmp != 0) { - return@Comparator cmp - } - - cmp = scores[l].compareTo(scores[r]) // Higher penalty first (this means lower performance score first) - if (cmp != 0) { - cmp - } else { - l.compareTo(r) - } - } - ) - - val newTargets = DoubleArray(size) - val newScores = DoubleArray(size) - val newMembers = arrayOfNulls<IntArray>(size) - - var nextId = 0 - val idMapping = ids.associateWith { nextId++ } - val membership = ids.associateWithTo(TreeMap()) { ArrayList<Int>() } - - for ((group, j) in indices.withIndex()) { - newTargets[group] = targets[j] - newScores[group] = scores[j] - val groupMembers = members[j] - val newGroupMembers = groupMembers.map { idMapping.getValue(it) }.toIntArray() - - newGroupMembers.sort() - newMembers[group] = newGroupMembers - - for (member in groupMembers) { - membership.getValue(member).add(group) - } - } - - @Suppress("UNCHECKED_CAST") - return VmInterferenceModel( - idMapping, - newMembers as Array<IntArray>, - membership.map { it.value.toIntArray() }.toTypedArray(), - newTargets, - newScores, - size - ) - } - - /** - * Helper function to grow the capacity of the internal arrays. - */ - private fun grow() { - val oldSize = _targets.size - val newSize = oldSize + (oldSize shr 1) - - _targets = _targets.copyOf(newSize) - _scores = _scores.copyOf(newSize) - } - - private companion object { - /** - * The initial capacity of the builder. - */ - const val INITIAL_CAPACITY = 256 - } - } -} diff --git a/opendc-simulator/opendc-simulator-compute/src/main/kotlin/org/opendc/simulator/compute/model/MachineModel.kt b/opendc-simulator/opendc-simulator-compute/src/main/kotlin/org/opendc/simulator/compute/model/MachineModel.kt deleted file mode 100644 index 22dcaef4..00000000 --- a/opendc-simulator/opendc-simulator-compute/src/main/kotlin/org/opendc/simulator/compute/model/MachineModel.kt +++ /dev/null @@ -1,54 +0,0 @@ -/* - * Copyright (c) 2021 AtLarge Research - * - * Permission is hereby granted, free of charge, to any person obtaining a copy - * of this software and associated documentation files (the "Software"), to deal - * in the Software without restriction, including without limitation the rights - * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell - * copies of the Software, and to permit persons to whom the Software is - * furnished to do so, subject to the following conditions: - * - * The above copyright notice and this permission notice shall be included in all - * copies or substantial portions of the Software. - * - * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR - * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, - * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE - * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER - * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, - * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE - * SOFTWARE. - */ - -package org.opendc.simulator.compute.model - -/** - * A description of the physical or virtual machine on which a bootable image runs. - * - * @property cpus The list of processing units available to the image. - * @property memory The list of memory units available to the image. - * @property net A list of network adapters available to the machine. - * @property storage A list of storage devices available to the machine. - */ -public data class MachineModel( - public val cpus: List<ProcessingUnit>, - public val memory: List<MemoryUnit>, - public val net: List<NetworkAdapter> = emptyList(), - public val storage: List<StorageDevice> = emptyList() -) { - /** - * Optimize the [MachineModel] by merging all resources of the same type into a single resource with the combined - * capacity. Such configurations can be simulated more efficiently by OpenDC. - */ - public fun optimize(): MachineModel { - val originalCpu = cpus[0] - val freq = cpus.sumOf { it.frequency } - val processingNode = originalCpu.node.copy(coreCount = 1) - val processingUnits = listOf(originalCpu.copy(frequency = freq, node = processingNode)) - - val memorySize = memory.sumOf { it.size } - val memoryUnits = listOf(MemoryUnit("Generic", "Generic", 3200.0, memorySize)) - - return MachineModel(processingUnits, memoryUnits) - } -} diff --git a/opendc-simulator/opendc-simulator-compute/src/main/kotlin/org/opendc/simulator/compute/model/MemoryUnit.kt b/opendc-simulator/opendc-simulator-compute/src/main/kotlin/org/opendc/simulator/compute/model/MemoryUnit.kt deleted file mode 100644 index bcbde5b1..00000000 --- a/opendc-simulator/opendc-simulator-compute/src/main/kotlin/org/opendc/simulator/compute/model/MemoryUnit.kt +++ /dev/null @@ -1,38 +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.simulator.compute.model - -/** - * A memory unit of a compute resource, either virtual or physical. - * - * @property vendor The vendor string of the memory. - * @property modelName The name of the memory model. - * @property speed The access speed of the memory in MHz. - * @property size The size of the memory unit in MBs. - */ -public data class MemoryUnit( - public val vendor: String, - public val modelName: String, - public val speed: Double, - public val size: Long -) diff --git a/opendc-simulator/opendc-simulator-compute/src/main/kotlin/org/opendc/simulator/compute/model/NetworkAdapter.kt b/opendc-simulator/opendc-simulator-compute/src/main/kotlin/org/opendc/simulator/compute/model/NetworkAdapter.kt deleted file mode 100644 index 46472144..00000000 --- a/opendc-simulator/opendc-simulator-compute/src/main/kotlin/org/opendc/simulator/compute/model/NetworkAdapter.kt +++ /dev/null @@ -1,36 +0,0 @@ -/* - * Copyright (c) 2021 AtLarge Research - * - * Permission is hereby granted, free of charge, to any person obtaining a copy - * of this software and associated documentation files (the "Software"), to deal - * in the Software without restriction, including without limitation the rights - * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell - * copies of the Software, and to permit persons to whom the Software is - * furnished to do so, subject to the following conditions: - * - * The above copyright notice and this permission notice shall be included in all - * copies or substantial portions of the Software. - * - * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR - * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, - * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE - * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER - * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, - * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE - * SOFTWARE. - */ - -package org.opendc.simulator.compute.model - -/** - * A description of a network adapter that is - * - * @property vendor The vendor of the network adapter. - * @property modelName The model name of the network adapter. - * @property bandwidth The bandwidth of the network adapter in Mbps. - */ -public data class NetworkAdapter( - public val vendor: String, - public val modelName: String, - public val bandwidth: Double -) diff --git a/opendc-simulator/opendc-simulator-compute/src/main/kotlin/org/opendc/simulator/compute/model/ProcessingNode.kt b/opendc-simulator/opendc-simulator-compute/src/main/kotlin/org/opendc/simulator/compute/model/ProcessingNode.kt deleted file mode 100644 index 58ed816c..00000000 --- a/opendc-simulator/opendc-simulator-compute/src/main/kotlin/org/opendc/simulator/compute/model/ProcessingNode.kt +++ /dev/null @@ -1,38 +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.simulator.compute.model - -/** - * A processing node/package/socket containing possibly several CPU cores. - * - * @property vendor The vendor string of the processor node. - * @property modelName The name of the processor node. - * @property arch The micro-architecture of the processor node. - * @property coreCount The number of logical CPUs in the processor node. - */ -public data class ProcessingNode( - public val vendor: String, - public val arch: String, - public val modelName: String, - public val coreCount: Int -) diff --git a/opendc-simulator/opendc-simulator-compute/src/main/kotlin/org/opendc/simulator/compute/model/ProcessingUnit.kt b/opendc-simulator/opendc-simulator-compute/src/main/kotlin/org/opendc/simulator/compute/model/ProcessingUnit.kt deleted file mode 100644 index 415e95e6..00000000 --- a/opendc-simulator/opendc-simulator-compute/src/main/kotlin/org/opendc/simulator/compute/model/ProcessingUnit.kt +++ /dev/null @@ -1,36 +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.simulator.compute.model - -/** - * A single logical compute unit of processor node, either virtual or physical. - * - * @property node The processing node containing the CPU core. - * @property id The identifier of the CPU core within the processing node. - * @property frequency The clock rate of the CPU in MHz. - */ -public data class ProcessingUnit( - public val node: ProcessingNode, - public val id: Int, - public val frequency: Double -) diff --git a/opendc-simulator/opendc-simulator-compute/src/main/kotlin/org/opendc/simulator/compute/model/StorageDevice.kt b/opendc-simulator/opendc-simulator-compute/src/main/kotlin/org/opendc/simulator/compute/model/StorageDevice.kt deleted file mode 100644 index 2621ad6d..00000000 --- a/opendc-simulator/opendc-simulator-compute/src/main/kotlin/org/opendc/simulator/compute/model/StorageDevice.kt +++ /dev/null @@ -1,40 +0,0 @@ -/* - * Copyright (c) 2021 AtLarge Research - * - * Permission is hereby granted, free of charge, to any person obtaining a copy - * of this software and associated documentation files (the "Software"), to deal - * in the Software without restriction, including without limitation the rights - * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell - * copies of the Software, and to permit persons to whom the Software is - * furnished to do so, subject to the following conditions: - * - * The above copyright notice and this permission notice shall be included in all - * copies or substantial portions of the Software. - * - * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR - * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, - * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE - * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER - * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, - * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE - * SOFTWARE. - */ - -package org.opendc.simulator.compute.model - -/** - * Model for a physical storage device attached to a machine. - * - * @property vendor The vendor of the storage device. - * @property modelName The model name of the device. - * @property capacity The capacity of the device. - * @property readBandwidth The read bandwidth of the device in MBps. - * @property writeBandwidth The write bandwidth of the device in MBps. - */ -public data class StorageDevice( - public val vendor: String, - public val modelName: String, - public val capacity: Double, - public val readBandwidth: Double, - public val writeBandwidth: Double -) diff --git a/opendc-simulator/opendc-simulator-compute/src/main/kotlin/org/opendc/simulator/compute/power/AsymptoticPowerModel.kt b/opendc-simulator/opendc-simulator-compute/src/main/kotlin/org/opendc/simulator/compute/power/AsymptoticPowerModel.kt deleted file mode 100644 index 46c397fe..00000000 --- a/opendc-simulator/opendc-simulator-compute/src/main/kotlin/org/opendc/simulator/compute/power/AsymptoticPowerModel.kt +++ /dev/null @@ -1,54 +0,0 @@ -/* - * Copyright (c) 2021 AtLarge Research - * - * Permission is hereby granted, free of charge, to any person obtaining a copy - * of this software and associated documentation files (the "Software"), to deal - * in the Software without restriction, including without limitation the rights - * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell - * copies of the Software, and to permit persons to whom the Software is - * furnished to do so, subject to the following conditions: - * - * The above copyright notice and this permission notice shall be included in all - * copies or substantial portions of the Software. - * - * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR - * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, - * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE - * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER - * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, - * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE - * SOFTWARE. - */ - -package org.opendc.simulator.compute.power - -import kotlin.math.E -import kotlin.math.pow - -/** - * The asymptotic power model partially adapted from GreenCloud. - * - * @param maxPower The maximum power draw of the server in W. - * @param idlePower The power draw of the server at its lowest utilization level in W. - * @param asymUtil A utilization level at which the server attains asymptotic, - * i.e., close to linear power consumption versus the offered load. - * For most of the CPUs,a is in [0.2, 0.5]. - * @param isDvfsEnabled A flag indicates whether DVFS is enabled. - */ -public class AsymptoticPowerModel( - private val maxPower: Double, - private val idlePower: Double, - private val asymUtil: Double, - private val isDvfsEnabled: Boolean -) : PowerModel { - private val factor: Double = (maxPower - idlePower) / 100 - - public override fun computePower(utilization: Double): Double = - if (isDvfsEnabled) { - idlePower + (factor * 100) / 2 * (1 + utilization.pow(3) - E.pow(-utilization.pow(3) / asymUtil)) - } else { - idlePower + (factor * 100) / 2 * (1 + utilization - E.pow(-utilization / asymUtil)) - } - - override fun toString(): String = "AsymptoticPowerModel[max=$maxPower,idle=$idlePower,asymptotic=$asymUtil]" -} diff --git a/opendc-simulator/opendc-simulator-compute/src/main/kotlin/org/opendc/simulator/compute/power/CubicPowerModel.kt b/opendc-simulator/opendc-simulator-compute/src/main/kotlin/org/opendc/simulator/compute/power/CubicPowerModel.kt deleted file mode 100644 index 0d3bf6cc..00000000 --- a/opendc-simulator/opendc-simulator-compute/src/main/kotlin/org/opendc/simulator/compute/power/CubicPowerModel.kt +++ /dev/null @@ -1,41 +0,0 @@ -/* - * Copyright (c) 2021 AtLarge Research - * - * Permission is hereby granted, free of charge, to any person obtaining a copy - * of this software and associated documentation files (the "Software"), to deal - * in the Software without restriction, including without limitation the rights - * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell - * copies of the Software, and to permit persons to whom the Software is - * furnished to do so, subject to the following conditions: - * - * The above copyright notice and this permission notice shall be included in all - * copies or substantial portions of the Software. - * - * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR - * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, - * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE - * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER - * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, - * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE - * SOFTWARE. - */ - -package org.opendc.simulator.compute.power - -import kotlin.math.pow - -/** - * The cubic power model partially adapted from CloudSim. - * - * @param maxPower The maximum power draw of the server in W. - * @param idlePower The power draw of the server at its lowest utilization level in W. - */ -public class CubicPowerModel(private val maxPower: Double, private val idlePower: Double) : PowerModel { - private val factor: Double = (maxPower - idlePower) / 100.0.pow(3) - - public override fun computePower(utilization: Double): Double { - return idlePower + factor * (utilization * 100).pow(3) - } - - override fun toString(): String = "CubicPowerModel[max=$maxPower,idle=$idlePower]" -} diff --git a/opendc-simulator/opendc-simulator-compute/src/main/kotlin/org/opendc/simulator/compute/power/InterpolationPowerModel.kt b/opendc-simulator/opendc-simulator-compute/src/main/kotlin/org/opendc/simulator/compute/power/InterpolationPowerModel.kt deleted file mode 100644 index b17b87a9..00000000 --- a/opendc-simulator/opendc-simulator-compute/src/main/kotlin/org/opendc/simulator/compute/power/InterpolationPowerModel.kt +++ /dev/null @@ -1,64 +0,0 @@ -/* - * Copyright (c) 2021 AtLarge Research - * - * Permission is hereby granted, free of charge, to any person obtaining a copy - * of this software and associated documentation files (the "Software"), to deal - * in the Software without restriction, including without limitation the rights - * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell - * copies of the Software, and to permit persons to whom the Software is - * furnished to do so, subject to the following conditions: - * - * The above copyright notice and this permission notice shall be included in all - * copies or substantial portions of the Software. - * - * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR - * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, - * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE - * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER - * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, - * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE - * SOFTWARE. - */ - -package org.opendc.simulator.compute.power - -import kotlin.math.ceil -import kotlin.math.floor -import kotlin.math.max -import kotlin.math.min - -/** - * The linear interpolation power model partially adapted from CloudSim. - * This model is developed to adopt the <a href="http://www.spec.org/power_ssj2008/">SPECpower benchmark</a>. - * - * @param powerValues A [List] of average active power measured by the power analyzer(s) and accumulated by the - * PTDaemon (Power and Temperature Daemon) for this measurement interval, displayed as watts (W). - * @see <a href="http://www.spec.org/power_ssj2008/results/res2011q1/">Machines used in the SPEC benchmark</a> - */ -public class InterpolationPowerModel(private val powerValues: List<Double>) : PowerModel { - public override fun computePower(utilization: Double): Double { - val clampedUtilization = min(1.0, max(0.0, utilization)) - val utilizationFlr = floor(clampedUtilization * 10).toInt() - val utilizationCil = ceil(clampedUtilization * 10).toInt() - val powerFlr: Double = getAveragePowerValue(utilizationFlr) - val powerCil: Double = getAveragePowerValue(utilizationCil) - val delta = (powerCil - powerFlr) / 10 - - return if (utilization % 0.1 == 0.0) { - getAveragePowerValue((clampedUtilization * 10).toInt()) - } else { - powerFlr + delta * (clampedUtilization - utilizationFlr.toDouble() / 10) * 100 - } - } - - override fun toString(): String = "InterpolationPowerModel[entries=${powerValues.size}]" - - /** - * Gets the power consumption for a given utilization percentage. - * - * @param index the utilization percentage in the scale from [0 to 10], - * where 10 means 100% of utilization. - * @return the power consumption for the given utilization percentage - */ - private fun getAveragePowerValue(index: Int): Double = powerValues[index] -} diff --git a/opendc-simulator/opendc-simulator-compute/src/main/kotlin/org/opendc/simulator/compute/power/LinearPowerModel.kt b/opendc-simulator/opendc-simulator-compute/src/main/kotlin/org/opendc/simulator/compute/power/LinearPowerModel.kt deleted file mode 100644 index dadc56ec..00000000 --- a/opendc-simulator/opendc-simulator-compute/src/main/kotlin/org/opendc/simulator/compute/power/LinearPowerModel.kt +++ /dev/null @@ -1,42 +0,0 @@ -/* - * Copyright (c) 2021 AtLarge Research - * - * Permission is hereby granted, free of charge, to any person obtaining a copy - * of this software and associated documentation files (the "Software"), to deal - * in the Software without restriction, including without limitation the rights - * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell - * copies of the Software, and to permit persons to whom the Software is - * furnished to do so, subject to the following conditions: - * - * The above copyright notice and this permission notice shall be included in all - * copies or substantial portions of the Software. - * - * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR - * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, - * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE - * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER - * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, - * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE - * SOFTWARE. - */ - -package org.opendc.simulator.compute.power - -/** - * The linear power model partially adapted from CloudSim. - * - * @param maxPower The maximum power draw of the server in W. - * @param idlePower The power draw of the server at its lowest utilization level in W. - */ -public class LinearPowerModel(private val maxPower: Double, private val idlePower: Double) : PowerModel { - /** - * The linear interpolation factor of the model. - */ - private val factor: Double = (maxPower - idlePower) / 100 - - public override fun computePower(utilization: Double): Double { - return idlePower + factor * utilization * 100 - } - - override fun toString(): String = "LinearPowerModel[max=$maxPower,idle=$idlePower]" -} diff --git a/opendc-simulator/opendc-simulator-compute/src/main/kotlin/org/opendc/simulator/compute/power/MsePowerModel.kt b/opendc-simulator/opendc-simulator-compute/src/main/kotlin/org/opendc/simulator/compute/power/MsePowerModel.kt deleted file mode 100644 index e9e72da8..00000000 --- a/opendc-simulator/opendc-simulator-compute/src/main/kotlin/org/opendc/simulator/compute/power/MsePowerModel.kt +++ /dev/null @@ -1,49 +0,0 @@ -/* - * Copyright (c) 2021 AtLarge Research - * - * Permission is hereby granted, free of charge, to any person obtaining a copy - * of this software and associated documentation files (the "Software"), to deal - * in the Software without restriction, including without limitation the rights - * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell - * copies of the Software, and to permit persons to whom the Software is - * furnished to do so, subject to the following conditions: - * - * The above copyright notice and this permission notice shall be included in all - * copies or substantial portions of the Software. - * - * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR - * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, - * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE - * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER - * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, - * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE - * SOFTWARE. - */ - -package org.opendc.simulator.compute.power - -import kotlin.math.pow - -/** - * The power model that minimizes the mean squared error (MSE) - * to the actual power measurement by tuning the calibration parameter. - * @see <a href="https://dl.acm.org/doi/abs/10.1145/1273440.1250665"> - * Fan et al., Power provisioning for a warehouse-sized computer, ACM SIGARCH'07</a> - * - * @param maxPower The maximum power draw of the server in W. - * @param idlePower The power draw of the server at its lowest utilization level in W. - * @param calibrationParam The parameter set to minimize the MSE. - */ -public class MsePowerModel( - private val maxPower: Double, - private val idlePower: Double, - private val calibrationParam: Double -) : PowerModel { - private val factor: Double = (maxPower - idlePower) / 100 - - public override fun computePower(utilization: Double): Double { - return idlePower + factor * (2 * utilization - utilization.pow(calibrationParam)) * 100 - } - - override fun toString(): String = "MsePowerModel[max=$maxPower,idle=$idlePower,MSE_param=$calibrationParam]" -} diff --git a/opendc-simulator/opendc-simulator-compute/src/main/kotlin/org/opendc/simulator/compute/power/PStatePowerDriver.kt b/opendc-simulator/opendc-simulator-compute/src/main/kotlin/org/opendc/simulator/compute/power/PStatePowerDriver.kt deleted file mode 100644 index ce7225d2..00000000 --- a/opendc-simulator/opendc-simulator-compute/src/main/kotlin/org/opendc/simulator/compute/power/PStatePowerDriver.kt +++ /dev/null @@ -1,60 +0,0 @@ -/* - * Copyright (c) 2021 AtLarge Research - * - * Permission is hereby granted, free of charge, to any person obtaining a copy - * of this software and associated documentation files (the "Software"), to deal - * in the Software without restriction, including without limitation the rights - * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell - * copies of the Software, and to permit persons to whom the Software is - * furnished to do so, subject to the following conditions: - * - * The above copyright notice and this permission notice shall be included in all - * copies or substantial portions of the Software. - * - * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR - * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, - * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE - * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER - * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, - * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE - * SOFTWARE. - */ - -package org.opendc.simulator.compute.power - -import org.opendc.simulator.compute.SimMachine -import org.opendc.simulator.compute.SimProcessingUnit -import java.util.TreeMap -import kotlin.math.max -import kotlin.math.min - -/** - * A [PowerDriver] that computes the power draw using multiple [PowerModel]s based on multiple frequency states. - * - * @param states A map describing the states of the driver. - */ -public class PStatePowerDriver(states: Map<Double, PowerModel>) : PowerDriver { - /** - * The P-States defined by the user and ordered by key. - */ - private val states: TreeMap<Double, PowerModel> = TreeMap(states) - - override fun createLogic(machine: SimMachine, cpus: List<SimProcessingUnit>): PowerDriver.Logic = object : PowerDriver.Logic { - override fun computePower(): Double { - var targetFreq = 0.0 - var totalSpeed = 0.0 - - for (cpu in cpus) { - targetFreq = max(cpu.capacity, targetFreq) - totalSpeed += cpu.rate - } - - val maxFreq = states.lastKey() - val (actualFreq, model) = states.ceilingEntry(min(maxFreq, targetFreq)) - val utilization = totalSpeed / (actualFreq * cpus.size) - return model.computePower(utilization) - } - } - - override fun toString(): String = "PStatePowerDriver[states=$states]" -} diff --git a/opendc-simulator/opendc-simulator-compute/src/main/kotlin/org/opendc/simulator/compute/power/PowerDriver.kt b/opendc-simulator/opendc-simulator-compute/src/main/kotlin/org/opendc/simulator/compute/power/PowerDriver.kt deleted file mode 100644 index 1a46dd4a..00000000 --- a/opendc-simulator/opendc-simulator-compute/src/main/kotlin/org/opendc/simulator/compute/power/PowerDriver.kt +++ /dev/null @@ -1,48 +0,0 @@ -/* - * Copyright (c) 2021 AtLarge Research - * - * Permission is hereby granted, free of charge, to any person obtaining a copy - * of this software and associated documentation files (the "Software"), to deal - * in the Software without restriction, including without limitation the rights - * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell - * copies of the Software, and to permit persons to whom the Software is - * furnished to do so, subject to the following conditions: - * - * The above copyright notice and this permission notice shall be included in all - * copies or substantial portions of the Software. - * - * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR - * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, - * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE - * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER - * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, - * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE - * SOFTWARE. - */ - -package org.opendc.simulator.compute.power - -import org.opendc.simulator.compute.SimMachine -import org.opendc.simulator.compute.SimProcessingUnit - -/** - * A [PowerDriver] is responsible for tracking the power usage for a component of the machine. - */ -public interface PowerDriver { - /** - * Create the driver logic for the specified [machine]. - */ - public fun createLogic(machine: SimMachine, cpus: List<SimProcessingUnit>): Logic - - /** - * The logic of the power driver. - */ - public interface Logic { - /** - * Compute the power consumption of the component. - * - * @return The power consumption of the component in W. - */ - public fun computePower(): Double - } -} diff --git a/opendc-simulator/opendc-simulator-compute/src/main/kotlin/org/opendc/simulator/compute/power/SimplePowerDriver.kt b/opendc-simulator/opendc-simulator-compute/src/main/kotlin/org/opendc/simulator/compute/power/SimplePowerDriver.kt deleted file mode 100644 index 34e91c35..00000000 --- a/opendc-simulator/opendc-simulator-compute/src/main/kotlin/org/opendc/simulator/compute/power/SimplePowerDriver.kt +++ /dev/null @@ -1,48 +0,0 @@ -/* - * Copyright (c) 2021 AtLarge Research - * - * Permission is hereby granted, free of charge, to any person obtaining a copy - * of this software and associated documentation files (the "Software"), to deal - * in the Software without restriction, including without limitation the rights - * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell - * copies of the Software, and to permit persons to whom the Software is - * furnished to do so, subject to the following conditions: - * - * The above copyright notice and this permission notice shall be included in all - * copies or substantial portions of the Software. - * - * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR - * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, - * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE - * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER - * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, - * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE - * SOFTWARE. - */ - -package org.opendc.simulator.compute.power - -import org.opendc.simulator.compute.SimMachine -import org.opendc.simulator.compute.SimProcessingUnit - -/** - * A [PowerDriver] that computes the power consumption based on a single specified [power model][model]. - */ -public class SimplePowerDriver(private val model: PowerModel) : PowerDriver { - override fun createLogic(machine: SimMachine, cpus: List<SimProcessingUnit>): PowerDriver.Logic = object : PowerDriver.Logic { - - override fun computePower(): Double { - var targetFreq = 0.0 - var totalSpeed = 0.0 - - for (cpu in cpus) { - targetFreq += cpu.capacity - totalSpeed += cpu.rate - } - - return model.computePower(totalSpeed / targetFreq) - } - } - - override fun toString(): String = "SimplePowerDriver[model=$model]" -} diff --git a/opendc-simulator/opendc-simulator-compute/src/main/kotlin/org/opendc/simulator/compute/power/SqrtPowerModel.kt b/opendc-simulator/opendc-simulator-compute/src/main/kotlin/org/opendc/simulator/compute/power/SqrtPowerModel.kt deleted file mode 100644 index 0665dbd9..00000000 --- a/opendc-simulator/opendc-simulator-compute/src/main/kotlin/org/opendc/simulator/compute/power/SqrtPowerModel.kt +++ /dev/null @@ -1,41 +0,0 @@ -/* - * Copyright (c) 2021 AtLarge Research - * - * Permission is hereby granted, free of charge, to any person obtaining a copy - * of this software and associated documentation files (the "Software"), to deal - * in the Software without restriction, including without limitation the rights - * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell - * copies of the Software, and to permit persons to whom the Software is - * furnished to do so, subject to the following conditions: - * - * The above copyright notice and this permission notice shall be included in all - * copies or substantial portions of the Software. - * - * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR - * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, - * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE - * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER - * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, - * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE - * SOFTWARE. - */ - -package org.opendc.simulator.compute.power - -import kotlin.math.sqrt - -/** - * The square root power model partially adapted from CloudSim. - * - * @param maxPower The maximum power draw of the server in W. - * @param idlePower The power draw of the server at its lowest utilization level in W. - */ -public class SqrtPowerModel(private val maxPower: Double, private val idlePower: Double) : PowerModel { - private val factor: Double = (maxPower - idlePower) / sqrt(100.0) - - override fun computePower(utilization: Double): Double { - return idlePower + factor * sqrt(utilization * 100) - } - - override fun toString(): String = "SqrtPowerModel[max=$maxPower,idle=$idlePower]" -} diff --git a/opendc-simulator/opendc-simulator-compute/src/main/kotlin/org/opendc/simulator/compute/power/SquarePowerModel.kt b/opendc-simulator/opendc-simulator-compute/src/main/kotlin/org/opendc/simulator/compute/power/SquarePowerModel.kt deleted file mode 100644 index e4ae88a9..00000000 --- a/opendc-simulator/opendc-simulator-compute/src/main/kotlin/org/opendc/simulator/compute/power/SquarePowerModel.kt +++ /dev/null @@ -1,41 +0,0 @@ -/* - * Copyright (c) 2021 AtLarge Research - * - * Permission is hereby granted, free of charge, to any person obtaining a copy - * of this software and associated documentation files (the "Software"), to deal - * in the Software without restriction, including without limitation the rights - * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell - * copies of the Software, and to permit persons to whom the Software is - * furnished to do so, subject to the following conditions: - * - * The above copyright notice and this permission notice shall be included in all - * copies or substantial portions of the Software. - * - * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR - * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, - * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE - * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER - * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, - * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE - * SOFTWARE. - */ - -package org.opendc.simulator.compute.power - -import kotlin.math.pow - -/** - * The square power model partially adapted from CloudSim. - * - * @param maxPower The maximum power draw of the server in W. - * @param idlePower The power draw of the server at its lowest utilization level in W. - */ -public class SquarePowerModel(private val maxPower: Double, private val idlePower: Double) : PowerModel { - private val factor: Double = (maxPower - idlePower) / 100.0.pow(2) - - override fun computePower(utilization: Double): Double { - return idlePower + factor * (utilization * 100).pow(2) - } - - override fun toString(): String = "SquarePowerModel[max=$maxPower,idle=$idlePower]" -} diff --git a/opendc-simulator/opendc-simulator-compute/src/main/kotlin/org/opendc/simulator/compute/power/ZeroIdlePowerDecorator.kt b/opendc-simulator/opendc-simulator-compute/src/main/kotlin/org/opendc/simulator/compute/power/ZeroIdlePowerDecorator.kt deleted file mode 100644 index 05ab4631..00000000 --- a/opendc-simulator/opendc-simulator-compute/src/main/kotlin/org/opendc/simulator/compute/power/ZeroIdlePowerDecorator.kt +++ /dev/null @@ -1,40 +0,0 @@ -/* - * Copyright (c) 2021 AtLarge Research - * - * Permission is hereby granted, free of charge, to any person obtaining a copy - * of this software and associated documentation files (the "Software"), to deal - * in the Software without restriction, including without limitation the rights - * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell - * copies of the Software, and to permit persons to whom the Software is - * furnished to do so, subject to the following conditions: - * - * The above copyright notice and this permission notice shall be included in all - * copies or substantial portions of the Software. - * - * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR - * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, - * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE - * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER - * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, - * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE - * SOFTWARE. - */ - -package org.opendc.simulator.compute.power - -/** - * A decorator for ignoring the idle power when computing energy consumption of components. - * - * @param delegate The [PowerModel] to delegate to. - */ -public class ZeroIdlePowerDecorator(private val delegate: PowerModel) : PowerModel { - override fun computePower(utilization: Double): Double { - return if (utilization == 0.0) { - 0.0 - } else { - delegate.computePower(utilization) - } - } - - override fun toString(): String = "ZeroIdlePowerDecorator[delegate=$delegate]" -} diff --git a/opendc-simulator/opendc-simulator-compute/src/main/kotlin/org/opendc/simulator/compute/workload/SimFlopsWorkload.kt b/opendc-simulator/opendc-simulator-compute/src/main/kotlin/org/opendc/simulator/compute/workload/SimFlopsWorkload.kt deleted file mode 100644 index 726d1f56..00000000 --- a/opendc-simulator/opendc-simulator-compute/src/main/kotlin/org/opendc/simulator/compute/workload/SimFlopsWorkload.kt +++ /dev/null @@ -1,54 +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.simulator.compute.workload - -import org.opendc.simulator.compute.SimMachineContext -import org.opendc.simulator.flow.source.FixedFlowSource - -/** - * A [SimWorkload] that models applications as a static number of floating point operations ([flops]) executed on - * multiple cores of a compute resource. - * - * @property flops The number of floating point operations to perform for this task in MFLOPs. - * @property utilization A model of the CPU utilization of the application. - */ -public class SimFlopsWorkload( - public val flops: Long, - public val utilization: Double = 0.8 -) : SimWorkload { - init { - require(flops >= 0) { "Number of FLOPs must be positive" } - require(utilization > 0.0 && utilization <= 1.0) { "Utilization must be in (0, 1]" } - } - - override fun onStart(ctx: SimMachineContext) { - val lifecycle = SimWorkloadLifecycle(ctx) - for (cpu in ctx.cpus) { - cpu.startConsumer(lifecycle.waitFor(FixedFlowSource(flops.toDouble() / ctx.cpus.size, utilization))) - } - } - - override fun onStop(ctx: SimMachineContext) {} - - override fun toString(): String = "SimFlopsWorkload(FLOPs=$flops,utilization=$utilization)" -} diff --git a/opendc-simulator/opendc-simulator-compute/src/main/kotlin/org/opendc/simulator/compute/workload/SimRuntimeWorkload.kt b/opendc-simulator/opendc-simulator-compute/src/main/kotlin/org/opendc/simulator/compute/workload/SimRuntimeWorkload.kt deleted file mode 100644 index 8a3f5f84..00000000 --- a/opendc-simulator/opendc-simulator-compute/src/main/kotlin/org/opendc/simulator/compute/workload/SimRuntimeWorkload.kt +++ /dev/null @@ -1,54 +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.simulator.compute.workload - -import org.opendc.simulator.compute.SimMachineContext -import org.opendc.simulator.flow.source.FixedFlowSource - -/** - * A [SimWorkload] that models application execution as a single duration. - * - * @property duration The duration of the workload. - * @property utilization The utilization of the application during runtime. - */ -public class SimRuntimeWorkload( - public val duration: Long, - public val utilization: Double = 0.8 -) : SimWorkload { - init { - require(duration >= 0) { "Duration must be non-negative" } - require(utilization > 0.0 && utilization <= 1.0) { "Utilization must be in (0, 1]" } - } - - override fun onStart(ctx: SimMachineContext) { - val lifecycle = SimWorkloadLifecycle(ctx) - for (cpu in ctx.cpus) { - val limit = cpu.capacity * utilization - cpu.startConsumer(lifecycle.waitFor(FixedFlowSource((limit / 1000) * duration, utilization))) - } - } - - override fun onStop(ctx: SimMachineContext) {} - - override fun toString(): String = "SimRuntimeWorkload(duration=$duration,utilization=$utilization)" -} diff --git a/opendc-simulator/opendc-simulator-compute/src/main/kotlin/org/opendc/simulator/compute/workload/SimTrace.kt b/opendc-simulator/opendc-simulator-compute/src/main/kotlin/org/opendc/simulator/compute/workload/SimTrace.kt deleted file mode 100644 index db6a4629..00000000 --- a/opendc-simulator/opendc-simulator-compute/src/main/kotlin/org/opendc/simulator/compute/workload/SimTrace.kt +++ /dev/null @@ -1,218 +0,0 @@ -/* - * Copyright (c) 2021 AtLarge Research - * - * Permission is hereby granted, free of charge, to any person obtaining a copy - * of this software and associated documentation files (the "Software"), to deal - * in the Software without restriction, including without limitation the rights - * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell - * copies of the Software, and to permit persons to whom the Software is - * furnished to do so, subject to the following conditions: - * - * The above copyright notice and this permission notice shall be included in all - * copies or substantial portions of the Software. - * - * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR - * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, - * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE - * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER - * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, - * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE - * SOFTWARE. - */ - -package org.opendc.simulator.compute.workload - -import org.opendc.simulator.compute.model.ProcessingUnit -import org.opendc.simulator.flow.FlowConnection -import org.opendc.simulator.flow.FlowSource -import kotlin.math.min - -/** - * A workload trace that describes the resource utilization over time in a collection of [SimTraceFragment]s. - * - * @param usageCol The column containing the CPU usage of each fragment (in MHz). - * @param deadlineCol The column containing the ending timestamp for each fragment (in epoch millis). - * @param coresCol The column containing the utilized cores. - * @param size The number of fragments in the trace. - */ -public class SimTrace( - private val usageCol: DoubleArray, - private val deadlineCol: LongArray, - private val coresCol: IntArray, - private val size: Int -) { - init { - require(size >= 0) { "Invalid trace size" } - require(usageCol.size >= size) { "Invalid number of usage entries" } - require(deadlineCol.size >= size) { "Invalid number of deadline entries" } - require(coresCol.size >= size) { "Invalid number of core entries" } - } - - public companion object { - /** - * Construct a [SimTrace] with the specified fragments. - */ - @JvmStatic - public fun ofFragments(fragments: List<SimTraceFragment>): SimTrace { - val size = fragments.size - val usageCol = DoubleArray(size) - val deadlineCol = LongArray(size) - val coresCol = IntArray(size) - - for (i in fragments.indices) { - val fragment = fragments[i] - usageCol[i] = fragment.usage - deadlineCol[i] = fragment.timestamp + fragment.duration - coresCol[i] = fragment.cores - } - - return SimTrace(usageCol, deadlineCol, coresCol, size) - } - - /** - * Construct a [SimTrace] with the specified fragments. - */ - @JvmStatic - public fun ofFragments(vararg fragments: SimTraceFragment): SimTrace { - val size = fragments.size - val usageCol = DoubleArray(size) - val deadlineCol = LongArray(size) - val coresCol = IntArray(size) - - for (i in fragments.indices) { - val fragment = fragments[i] - usageCol[i] = fragment.usage - deadlineCol[i] = fragment.timestamp + fragment.duration - coresCol[i] = fragment.cores - } - - return SimTrace(usageCol, deadlineCol, coresCol, size) - } - - /** - * Create a [SimTrace.Builder] instance. - */ - @JvmStatic - public fun builder(): Builder = Builder() - } - - /** - * Construct a new [FlowSource] for the specified [cpu]. - * - * @param cpu The [ProcessingUnit] for which to create the source. - * @param offset The time offset to use for the trace. - */ - public fun newSource(cpu: ProcessingUnit, offset: Long): FlowSource { - return CpuConsumer(cpu, offset, usageCol, deadlineCol, coresCol, size) - } - - /** - * A builder class for a [SimTrace]. - */ - public class Builder internal constructor() { - /** - * The columns of the trace. - */ - private var usageCol: DoubleArray = DoubleArray(16) - private var deadlineCol: LongArray = LongArray(16) - private var coresCol: IntArray = IntArray(16) - - /** - * The number of entries in the trace. - */ - private var size = 0 - - /** - * Add the specified [SimTraceFragment] to the trace. - */ - public fun add(fragment: SimTraceFragment) { - add(fragment.timestamp + fragment.duration, fragment.usage, fragment.cores) - } - - /** - * Add a fragment to the trace. - * - * @param deadline Timestamp at which the fragment ends (in epoch millis). - * @param usage CPU usage of this fragment. - * @param cores Number of cores used. - */ - public fun add(deadline: Long, usage: Double, cores: Int) { - val size = size - - if (size == usageCol.size) { - grow() - } - - deadlineCol[size] = deadline - usageCol[size] = usage - coresCol[size] = cores - - this.size++ - } - - /** - * Helper function to grow the capacity of the column arrays. - */ - private fun grow() { - val arraySize = usageCol.size - val newSize = arraySize + (arraySize shr 1) - - usageCol = usageCol.copyOf(newSize) - deadlineCol = deadlineCol.copyOf(newSize) - coresCol = coresCol.copyOf(newSize) - } - - /** - * Construct the immutable [SimTrace]. - */ - public fun build(): SimTrace { - return SimTrace(usageCol, deadlineCol, coresCol, size) - } - } - - /** - * A CPU consumer for the trace workload. - */ - private class CpuConsumer( - cpu: ProcessingUnit, - private val offset: Long, - private val usageCol: DoubleArray, - private val deadlineCol: LongArray, - private val coresCol: IntArray, - private val size: Int - ) : FlowSource { - private val id = cpu.id - private val coreCount = cpu.node.coreCount - - /** - * The index in the trace. - */ - private var _idx = 0 - - override fun onPull(conn: FlowConnection, now: Long): Long { - val size = size - val nowOffset = now - offset - - var idx = _idx - val deadlines = deadlineCol - var deadline = deadlines[idx] - - while (deadline <= nowOffset && ++idx < size) { - deadline = deadlines[idx] - } - - if (idx >= size) { - conn.close() - return Long.MAX_VALUE - } - - _idx = idx - - val cores = min(coreCount, coresCol[idx]) - val usage = usageCol[idx] - - conn.push(if (id < cores) usage / cores else 0.0) - return deadline - nowOffset - } - } -} diff --git a/opendc-simulator/opendc-simulator-compute/src/main/kotlin/org/opendc/simulator/compute/workload/SimTraceFragment.kt b/opendc-simulator/opendc-simulator-compute/src/main/kotlin/org/opendc/simulator/compute/workload/SimTraceFragment.kt deleted file mode 100644 index 5285847f..00000000 --- a/opendc-simulator/opendc-simulator-compute/src/main/kotlin/org/opendc/simulator/compute/workload/SimTraceFragment.kt +++ /dev/null @@ -1,38 +0,0 @@ -/* - * Copyright (c) 2021 AtLarge Research - * - * Permission is hereby granted, free of charge, to any person obtaining a copy - * of this software and associated documentation files (the "Software"), to deal - * in the Software without restriction, including without limitation the rights - * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell - * copies of the Software, and to permit persons to whom the Software is - * furnished to do so, subject to the following conditions: - * - * The above copyright notice and this permission notice shall be included in all - * copies or substantial portions of the Software. - * - * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR - * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, - * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE - * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER - * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, - * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE - * SOFTWARE. - */ - -package org.opendc.simulator.compute.workload - -/** - * A fragment of the workload trace. - * - * @param timestamp The timestamp at which the fragment starts (in epoch millis). - * @param duration The duration of the fragment (in milliseconds). - * @param usage The CPU usage during the fragment (in MHz). - * @param cores The amount of cores utilized during the fragment. - */ -public data class SimTraceFragment( - @JvmField val timestamp: Long, - @JvmField val duration: Long, - @JvmField val usage: Double, - @JvmField val cores: Int -) diff --git a/opendc-simulator/opendc-simulator-compute/src/main/kotlin/org/opendc/simulator/compute/workload/SimTraceWorkload.kt b/opendc-simulator/opendc-simulator-compute/src/main/kotlin/org/opendc/simulator/compute/workload/SimTraceWorkload.kt deleted file mode 100644 index ce04a790..00000000 --- a/opendc-simulator/opendc-simulator-compute/src/main/kotlin/org/opendc/simulator/compute/workload/SimTraceWorkload.kt +++ /dev/null @@ -1,46 +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.simulator.compute.workload - -import org.opendc.simulator.compute.SimMachineContext - -/** - * A [SimWorkload] that replays a workload trace consisting of multiple fragments, each indicating the resource - * consumption for some period of time. - * - * @param trace The trace of fragments to use. - * @param offset The offset for the timestamps. - */ -public class SimTraceWorkload(private val trace: SimTrace, private val offset: Long = 0L) : SimWorkload { - override fun onStart(ctx: SimMachineContext) { - val lifecycle = SimWorkloadLifecycle(ctx) - - for (cpu in ctx.cpus) { - cpu.startConsumer(lifecycle.waitFor(trace.newSource(cpu.model, offset))) - } - } - - override fun onStop(ctx: SimMachineContext) {} - - override fun toString(): String = "SimTraceWorkload" -} diff --git a/opendc-simulator/opendc-simulator-compute/src/main/kotlin/org/opendc/simulator/compute/workload/SimWorkloadLifecycle.kt b/opendc-simulator/opendc-simulator-compute/src/main/kotlin/org/opendc/simulator/compute/workload/SimWorkloadLifecycle.kt deleted file mode 100644 index 46113bb0..00000000 --- a/opendc-simulator/opendc-simulator-compute/src/main/kotlin/org/opendc/simulator/compute/workload/SimWorkloadLifecycle.kt +++ /dev/null @@ -1,82 +0,0 @@ -/* - * Copyright (c) 2021 AtLarge Research - * - * Permission is hereby granted, free of charge, to any person obtaining a copy - * of this software and associated documentation files (the "Software"), to deal - * in the Software without restriction, including without limitation the rights - * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell - * copies of the Software, and to permit persons to whom the Software is - * furnished to do so, subject to the following conditions: - * - * The above copyright notice and this permission notice shall be included in all - * copies or substantial portions of the Software. - * - * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR - * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, - * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE - * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER - * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, - * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE - * SOFTWARE. - */ - -package org.opendc.simulator.compute.workload - -import org.opendc.simulator.compute.SimMachineContext -import org.opendc.simulator.flow.FlowConnection -import org.opendc.simulator.flow.FlowSource - -/** - * A helper class to manage the lifecycle of a [SimWorkload] - */ -public class SimWorkloadLifecycle(private val ctx: SimMachineContext) { - /** - * The resource consumers which represent the lifecycle of the workload. - */ - private val waiting = HashSet<Wrapper>() - - /** - * Wait for the specified [source] to complete before ending the lifecycle of the workload. - */ - public fun waitFor(source: FlowSource): FlowSource { - val wrapper = Wrapper(source) - waiting.add(wrapper) - return wrapper - } - - /** - * Complete the specified [Wrapper]. - */ - private fun complete(wrapper: Wrapper) { - if (waiting.remove(wrapper) && waiting.isEmpty()) { - ctx.close() - } - } - - /** - * A [FlowSource] that wraps [delegate] and informs [SimWorkloadLifecycle] that is has completed. - */ - private inner class Wrapper(private val delegate: FlowSource) : FlowSource { - override fun onStart(conn: FlowConnection, now: Long) { - delegate.onStart(conn, now) - } - - override fun onPull(conn: FlowConnection, now: Long): Long { - return delegate.onPull(conn, now) - } - - override fun onConverge(conn: FlowConnection, now: Long) { - delegate.onConverge(conn, now) - } - - override fun onStop(conn: FlowConnection, now: Long) { - try { - delegate.onStop(conn, now) - } finally { - complete(this) - } - } - - override fun toString(): String = "SimWorkloadLifecycle.Wrapper[delegate=$delegate]" - } -} diff --git a/opendc-simulator/opendc-simulator-compute/src/test/kotlin/org/opendc/simulator/compute/SimMachineTest.kt b/opendc-simulator/opendc-simulator-compute/src/test/kotlin/org/opendc/simulator/compute/SimMachineTest.kt index 1eddf82c..f0aae15b 100644 --- a/opendc-simulator/opendc-simulator-compute/src/test/kotlin/org/opendc/simulator/compute/SimMachineTest.kt +++ b/opendc-simulator/opendc-simulator-compute/src/test/kotlin/org/opendc/simulator/compute/SimMachineTest.kt @@ -26,6 +26,7 @@ import kotlinx.coroutines.CancellationException import kotlinx.coroutines.cancel import kotlinx.coroutines.coroutineScope import kotlinx.coroutines.launch +import kotlinx.coroutines.yield import org.junit.jupiter.api.Assertions.assertAll import org.junit.jupiter.api.Assertions.assertEquals import org.junit.jupiter.api.BeforeEach @@ -38,17 +39,16 @@ import org.opendc.simulator.compute.model.NetworkAdapter import org.opendc.simulator.compute.model.ProcessingNode import org.opendc.simulator.compute.model.ProcessingUnit import org.opendc.simulator.compute.model.StorageDevice -import org.opendc.simulator.compute.power.ConstantPowerModel -import org.opendc.simulator.compute.power.LinearPowerModel -import org.opendc.simulator.compute.power.SimplePowerDriver +import org.opendc.simulator.compute.power.CpuPowerModels import org.opendc.simulator.compute.workload.SimFlopsWorkload +import org.opendc.simulator.compute.workload.SimTrace import org.opendc.simulator.compute.workload.SimWorkload -import org.opendc.simulator.compute.workload.SimWorkloadLifecycle -import org.opendc.simulator.flow.FlowEngine -import org.opendc.simulator.flow.source.FixedFlowSource +import org.opendc.simulator.flow2.FlowEngine +import org.opendc.simulator.flow2.source.SimpleFlowSource import org.opendc.simulator.kotlin.runSimulation import org.opendc.simulator.network.SimNetworkSink import org.opendc.simulator.power.SimPowerSource +import java.util.concurrent.ThreadLocalRandom /** * Test suite for the [SimBareMetalMachine] class. @@ -61,41 +61,69 @@ class SimMachineTest { val cpuNode = ProcessingNode("Intel", "Xeon", "amd64", 2) machineModel = MachineModel( - cpus = List(cpuNode.coreCount) { ProcessingUnit(cpuNode, it, 1000.0) }, - memory = List(4) { MemoryUnit("Crucial", "MTA18ASF4G72AZ-3G2B1", 3200.0, 32_000) }, - net = listOf(NetworkAdapter("Mellanox", "ConnectX-5", 25000.0)), - storage = listOf(StorageDevice("Samsung", "EVO", 1000.0, 250.0, 250.0)) + /*cpus*/ List(cpuNode.coreCount) { ProcessingUnit(cpuNode, it, 1000.0) }, + /*memory*/ List(4) { MemoryUnit("Crucial", "MTA18ASF4G72AZ-3G2B1", 3200.0, 32_000) }, + /*net*/ listOf(NetworkAdapter("Mellanox", "ConnectX-5", 25000.0)), + /*storage*/ listOf(StorageDevice("Samsung", "EVO", 1000.0, 250.0, 250.0)) ) } @Test fun testFlopsWorkload() = runSimulation { - val machine = SimBareMetalMachine( - FlowEngine(coroutineContext, clock), - machineModel, - SimplePowerDriver(ConstantPowerModel(0.0)) + val engine = FlowEngine.create(coroutineContext, clock) + val graph = engine.newGraph() + + val machine = SimBareMetalMachine.create( + graph, + machineModel ) - machine.runWorkload(SimFlopsWorkload(2_000, utilization = 1.0)) + machine.runWorkload(SimFlopsWorkload(2_000, /*utilization*/ 1.0)) // Two cores execute 1000 MFlOps per second (1000 ms) assertEquals(1000, clock.millis()) } @Test + fun testTraceWorkload() = runSimulation { + val random = ThreadLocalRandom.current() + val builder = SimTrace.builder() + repeat(1000000) { + val timestamp = it.toLong() * 1000 + val deadline = timestamp + 1000 + builder.add(deadline, random.nextDouble(0.0, 4500.0), 1) + } + val trace = builder.build() + + val engine = FlowEngine.create(coroutineContext, clock) + val graph = engine.newGraph() + val machine = SimBareMetalMachine.create( + graph, + machineModel + ) + + machine.runWorkload(trace.createWorkload(0)) + + // Two cores execute 1000 MFlOps per second (1000 ms) + assertEquals(1000000000, clock.millis()) + } + + @Test fun testDualSocketMachine() = runSimulation { + val engine = FlowEngine.create(coroutineContext, clock) + val graph = engine.newGraph() + val cpuNode = machineModel.cpus[0].node val machineModel = MachineModel( - cpus = List(cpuNode.coreCount * 2) { ProcessingUnit(cpuNode, it % 2, 1000.0) }, - memory = List(4) { MemoryUnit("Crucial", "MTA18ASF4G72AZ-3G2B1", 3200.0, 32_000) } + /*cpus*/ List(cpuNode.coreCount * 2) { ProcessingUnit(cpuNode, it % 2, 1000.0) }, + /*memory*/ List(4) { MemoryUnit("Crucial", "MTA18ASF4G72AZ-3G2B1", 3200.0, 32_000) } ) - val machine = SimBareMetalMachine( - FlowEngine(coroutineContext, clock), - machineModel, - SimplePowerDriver(ConstantPowerModel(0.0)) + val machine = SimBareMetalMachine.create( + graph, + machineModel ) - machine.runWorkload(SimFlopsWorkload(2_000, utilization = 1.0)) + machine.runWorkload(SimFlopsWorkload(2_000, /*utilization*/ 1.0)) // Two sockets with two cores execute 2000 MFlOps per second (500 ms) assertEquals(500, clock.millis()) @@ -103,42 +131,47 @@ class SimMachineTest { @Test fun testPower() = runSimulation { - val engine = FlowEngine(coroutineContext, clock) - val machine = SimBareMetalMachine( - engine, + val engine = FlowEngine.create(coroutineContext, clock) + val graph = engine.newGraph() + val machine = SimBareMetalMachine.create( + graph, machineModel, - SimplePowerDriver(LinearPowerModel(100.0, 50.0)) + SimPsuFactories.simple(CpuPowerModels.linear(100.0, 50.0)) ) - val source = SimPowerSource(engine, capacity = 1000.0) + val source = SimPowerSource(graph, /*capacity*/ 1000.0f) source.connect(machine.psu) coroutineScope { - launch { machine.runWorkload(SimFlopsWorkload(2_000, utilization = 1.0)) } + launch { machine.runWorkload(SimFlopsWorkload(2_000, /*utilization*/ 1.0)) } + + yield() assertAll( - { assertEquals(100.0, machine.psu.powerDraw) }, - { assertEquals(100.0, source.powerDraw) } + { assertEquals(100.0, machine.psu.powerUsage) }, + { assertEquals(100.0f, source.powerDraw) } ) } } @Test fun testCapacityClamp() = runSimulation { - val machine = SimBareMetalMachine( - FlowEngine(coroutineContext, clock), - machineModel, - SimplePowerDriver(ConstantPowerModel(0.0)) + val engine = FlowEngine.create(coroutineContext, clock) + val graph = engine.newGraph() + + val machine = SimBareMetalMachine.create( + graph, + machineModel ) machine.runWorkload(object : SimWorkload { override fun onStart(ctx: SimMachineContext) { val cpu = ctx.cpus[0] - cpu.capacity = cpu.model.frequency + 1000.0 - assertEquals(cpu.model.frequency, cpu.capacity) - cpu.capacity = -1.0 - assertEquals(0.0, cpu.capacity) + cpu.frequency = (cpu.model.frequency + 1000.0) + assertEquals(cpu.model.frequency, cpu.frequency) + cpu.frequency = -1.0 + assertEquals(0.0, cpu.frequency) - ctx.close() + ctx.shutdown() } override fun onStop(ctx: SimMachineContext) {} @@ -147,16 +180,18 @@ class SimMachineTest { @Test fun testMemory() = runSimulation { - val machine = SimBareMetalMachine( - FlowEngine(coroutineContext, clock), - machineModel, - SimplePowerDriver(ConstantPowerModel(0.0)) + val engine = FlowEngine.create(coroutineContext, clock) + val graph = engine.newGraph() + + val machine = SimBareMetalMachine.create( + graph, + machineModel ) machine.runWorkload(object : SimWorkload { override fun onStart(ctx: SimMachineContext) { assertEquals(32_000 * 4.0, ctx.memory.capacity) - ctx.close() + ctx.shutdown() } override fun onStop(ctx: SimMachineContext) {} @@ -165,104 +200,111 @@ class SimMachineTest { @Test fun testMemoryUsage() = runSimulation { - val machine = SimBareMetalMachine( - FlowEngine(coroutineContext, clock), - machineModel, - SimplePowerDriver(ConstantPowerModel(0.0)) + val engine = FlowEngine.create(coroutineContext, clock) + val graph = engine.newGraph() + + val machine = SimBareMetalMachine.create( + graph, + machineModel ) machine.runWorkload(object : SimWorkload { override fun onStart(ctx: SimMachineContext) { - val lifecycle = SimWorkloadLifecycle(ctx) - ctx.memory.startConsumer(lifecycle.waitFor(FixedFlowSource(ctx.memory.capacity, utilization = 0.8))) + val source = SimpleFlowSource(ctx.graph, ctx.memory.capacity.toFloat(), 1.0f) { ctx.shutdown() } + ctx.graph.connect(source.output, ctx.memory.input) } override fun onStop(ctx: SimMachineContext) {} }) - assertEquals(1250, clock.millis()) + assertEquals(1000, clock.millis()) } @Test fun testNetUsage() = runSimulation { - val engine = FlowEngine(coroutineContext, clock) - val machine = SimBareMetalMachine( - engine, - machineModel, - SimplePowerDriver(ConstantPowerModel(0.0)) + val engine = FlowEngine.create(coroutineContext, clock) + val graph = engine.newGraph() + + val machine = SimBareMetalMachine.create( + graph, + machineModel ) val adapter = (machine.peripherals[0] as SimNetworkAdapter) - adapter.connect(SimNetworkSink(engine, adapter.bandwidth)) + adapter.connect(SimNetworkSink(graph, adapter.bandwidth.toFloat())) machine.runWorkload(object : SimWorkload { override fun onStart(ctx: SimMachineContext) { - val lifecycle = SimWorkloadLifecycle(ctx) - val iface = ctx.net[0] - iface.tx.startConsumer(lifecycle.waitFor(FixedFlowSource(iface.bandwidth, utilization = 0.8))) + val iface = ctx.networkInterfaces[0] + val source = SimpleFlowSource(ctx.graph, 800.0f, 0.8f) { ctx.shutdown(); it.close(); } + ctx.graph.connect(source.output, iface.tx) } override fun onStop(ctx: SimMachineContext) {} }) - assertEquals(1250, clock.millis()) + assertEquals(40, clock.millis()) } @Test fun testDiskReadUsage() = runSimulation { - val engine = FlowEngine(coroutineContext, clock) - val machine = SimBareMetalMachine( - engine, - machineModel, - SimplePowerDriver(ConstantPowerModel(0.0)) + val engine = FlowEngine.create(coroutineContext, clock) + val graph = engine.newGraph() + + val machine = SimBareMetalMachine.create( + graph, + machineModel ) machine.runWorkload(object : SimWorkload { override fun onStart(ctx: SimMachineContext) { - val lifecycle = SimWorkloadLifecycle(ctx) - val disk = ctx.storage[0] - disk.read.startConsumer(lifecycle.waitFor(FixedFlowSource(disk.read.capacity, utilization = 0.8))) + val disk = ctx.storageInterfaces[0] + val source = SimpleFlowSource(ctx.graph, 800.0f, 0.8f) { ctx.shutdown() } + ctx.graph.connect(source.output, disk.read) } override fun onStop(ctx: SimMachineContext) {} }) - assertEquals(1250, clock.millis()) + assertEquals(4000, clock.millis()) } @Test fun testDiskWriteUsage() = runSimulation { - val engine = FlowEngine(coroutineContext, clock) - val machine = SimBareMetalMachine( - engine, - machineModel, - SimplePowerDriver(ConstantPowerModel(0.0)) + val engine = FlowEngine.create(coroutineContext, clock) + val graph = engine.newGraph() + + val machine = SimBareMetalMachine.create( + graph, + machineModel ) machine.runWorkload(object : SimWorkload { override fun onStart(ctx: SimMachineContext) { - val lifecycle = SimWorkloadLifecycle(ctx) - val disk = ctx.storage[0] - disk.write.startConsumer(lifecycle.waitFor(FixedFlowSource(disk.write.capacity, utilization = 0.8))) + val disk = ctx.storageInterfaces[0] + val source = SimpleFlowSource(ctx.graph, 800.0f, 0.8f) { ctx.shutdown() } + ctx.graph.connect(source.output, disk.write) } override fun onStop(ctx: SimMachineContext) {} }) - assertEquals(1250, clock.millis()) + assertEquals(4000, clock.millis()) } @Test fun testCancellation() = runSimulation { - val machine = SimBareMetalMachine( - FlowEngine(coroutineContext, clock), - machineModel, - SimplePowerDriver(ConstantPowerModel(0.0)) + val engine = FlowEngine.create(coroutineContext, clock) + val graph = engine.newGraph() + + val machine = SimBareMetalMachine.create( + graph, + machineModel ) try { coroutineScope { - launch { machine.runWorkload(SimFlopsWorkload(2_000, utilization = 1.0)) } + launch { machine.runWorkload(SimFlopsWorkload(2_000, /*utilization*/ 1.0)) } cancel() } } catch (_: CancellationException) { @@ -274,19 +316,21 @@ class SimMachineTest { @Test fun testConcurrentRuns() = runSimulation { - val machine = SimBareMetalMachine( - FlowEngine(coroutineContext, clock), - machineModel, - SimplePowerDriver(ConstantPowerModel(0.0)) + val engine = FlowEngine.create(coroutineContext, clock) + val graph = engine.newGraph() + + val machine = SimBareMetalMachine.create( + graph, + machineModel ) coroutineScope { launch { - machine.runWorkload(SimFlopsWorkload(2_000, utilization = 1.0)) + machine.runWorkload(SimFlopsWorkload(2_000, /*utilization*/ 1.0)) } assertThrows<IllegalStateException> { - machine.runWorkload(SimFlopsWorkload(2_000, utilization = 1.0)) + machine.runWorkload(SimFlopsWorkload(2_000, /*utilization*/ 1.0)) } } } diff --git a/opendc-simulator/opendc-simulator-compute/src/test/kotlin/org/opendc/simulator/compute/device/SimPsuTest.kt b/opendc-simulator/opendc-simulator-compute/src/test/kotlin/org/opendc/simulator/compute/device/SimPsuTest.kt deleted file mode 100644 index 0a6cb29f..00000000 --- a/opendc-simulator/opendc-simulator-compute/src/test/kotlin/org/opendc/simulator/compute/device/SimPsuTest.kt +++ /dev/null @@ -1,102 +0,0 @@ -/* - * Copyright (c) 2021 AtLarge Research - * - * Permission is hereby granted, free of charge, to any person obtaining a copy - * of this software and associated documentation files (the "Software"), to deal - * in the Software without restriction, including without limitation the rights - * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell - * copies of the Software, and to permit persons to whom the Software is - * furnished to do so, subject to the following conditions: - * - * The above copyright notice and this permission notice shall be included in all - * copies or substantial portions of the Software. - * - * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR - * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, - * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE - * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER - * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, - * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE - * SOFTWARE. - */ - -package org.opendc.simulator.compute.device - -import io.mockk.every -import io.mockk.mockk -import org.junit.jupiter.api.Assertions.assertEquals -import org.junit.jupiter.api.Test -import org.junit.jupiter.api.assertThrows -import org.opendc.simulator.compute.power.PowerDriver -import org.opendc.simulator.flow.FlowEngine -import org.opendc.simulator.kotlin.runSimulation -import org.opendc.simulator.power.SimPowerSource - -/** - * Test suite for [SimPsu] - */ -internal class SimPsuTest { - - @Test - fun testInvalidInput() { - assertThrows<IllegalArgumentException> { SimPsu(1.0, emptyMap()) } - } - - @Test - fun testDoubleConnect() { - val psu = SimPsu(1.0, mapOf(0.0 to 1.0)) - val cpuLogic = mockk<PowerDriver.Logic>() - psu.connect(cpuLogic) - assertThrows<IllegalStateException> { psu.connect(mockk()) } - } - - @Test - fun testPsuIdle() = runSimulation { - val ratedOutputPower = 240.0 - val energyEfficiency = mapOf(0.0 to 1.0) - - val engine = FlowEngine(coroutineContext, clock) - val source = SimPowerSource(engine, capacity = ratedOutputPower) - - val cpuLogic = mockk<PowerDriver.Logic>() - every { cpuLogic.computePower() } returns 0.0 - - val psu = SimPsu(ratedOutputPower, energyEfficiency) - psu.connect(cpuLogic) - source.connect(psu) - - assertEquals(0.0, source.powerDraw, 0.01) - } - - @Test - fun testPsuPowerLoss() = runSimulation { - val ratedOutputPower = 240.0 - // Efficiency of 80 Plus Titanium PSU - val energyEfficiency = sortedMapOf( - 0.3 to 0.9, - 0.7 to 0.92, - 1.0 to 0.94 - ) - - val engine = FlowEngine(coroutineContext, clock) - val source = SimPowerSource(engine, capacity = ratedOutputPower) - - val cpuLogic = mockk<PowerDriver.Logic>() - every { cpuLogic.computePower() } returnsMany listOf(50.0, 100.0, 150.0, 200.0) - - val psu = SimPsu(ratedOutputPower, energyEfficiency) - psu.connect(cpuLogic) - source.connect(psu) - - assertEquals(55.55, source.powerDraw, 0.01) - - psu.update() - assertEquals(108.695, source.powerDraw, 0.01) - - psu.update() - assertEquals(163.043, source.powerDraw, 0.01) - - psu.update() - assertEquals(212.765, source.powerDraw, 0.01) - } -} diff --git a/opendc-simulator/opendc-simulator-compute/src/test/kotlin/org/opendc/simulator/compute/kernel/SimFairShareHypervisorTest.kt b/opendc-simulator/opendc-simulator-compute/src/test/kotlin/org/opendc/simulator/compute/kernel/SimFairShareHypervisorTest.kt index 6b498119..79669d40 100644 --- a/opendc-simulator/opendc-simulator-compute/src/test/kotlin/org/opendc/simulator/compute/kernel/SimFairShareHypervisorTest.kt +++ b/opendc-simulator/opendc-simulator-compute/src/test/kotlin/org/opendc/simulator/compute/kernel/SimFairShareHypervisorTest.kt @@ -31,20 +31,17 @@ import org.junit.jupiter.api.Test import org.junit.jupiter.api.assertAll import org.junit.jupiter.api.assertDoesNotThrow import org.opendc.simulator.compute.SimBareMetalMachine -import org.opendc.simulator.compute.kernel.cpufreq.PerformanceScalingGovernor +import org.opendc.simulator.compute.kernel.cpufreq.ScalingGovernors import org.opendc.simulator.compute.kernel.interference.VmInterferenceModel import org.opendc.simulator.compute.model.MachineModel import org.opendc.simulator.compute.model.MemoryUnit import org.opendc.simulator.compute.model.ProcessingNode import org.opendc.simulator.compute.model.ProcessingUnit -import org.opendc.simulator.compute.power.ConstantPowerModel -import org.opendc.simulator.compute.power.SimplePowerDriver import org.opendc.simulator.compute.runWorkload import org.opendc.simulator.compute.workload.SimTrace import org.opendc.simulator.compute.workload.SimTraceFragment -import org.opendc.simulator.compute.workload.SimTraceWorkload -import org.opendc.simulator.flow.FlowEngine -import org.opendc.simulator.flow.mux.FlowMultiplexerFactory +import org.opendc.simulator.flow2.FlowEngine +import org.opendc.simulator.flow2.mux.FlowMultiplexerFactory import org.opendc.simulator.kotlin.runSimulation import java.util.SplittableRandom @@ -58,8 +55,8 @@ internal class SimFairShareHypervisorTest { fun setUp() { val cpuNode = ProcessingNode("Intel", "Xeon", "amd64", 1) model = MachineModel( - cpus = List(cpuNode.coreCount) { ProcessingUnit(cpuNode, it, 3200.0) }, - memory = List(4) { MemoryUnit("Crucial", "MTA18ASF4G72AZ-3G2B1", 3200.0, 32_000) } + /*cpus*/ List(cpuNode.coreCount) { ProcessingUnit(cpuNode, it, 3200.0) }, + /*memory*/ List(4) { MemoryUnit("Crucial", "MTA18ASF4G72AZ-3G2B1", 3200.0, 32_000) } ) } @@ -70,23 +67,20 @@ internal class SimFairShareHypervisorTest { fun testOvercommittedSingle() = runSimulation { val duration = 5 * 60L val workloadA = - SimTraceWorkload( - SimTrace.ofFragments( - SimTraceFragment(0, duration * 1000, 28.0, 1), - SimTraceFragment(duration * 1000, duration * 1000, 3500.0, 1), - SimTraceFragment(duration * 2000, duration * 1000, 0.0, 1), - SimTraceFragment(duration * 3000, duration * 1000, 183.0, 1) - ) - ) - - val engine = FlowEngine(coroutineContext, clock) - val machine = SimBareMetalMachine(engine, model, SimplePowerDriver(ConstantPowerModel(0.0))) - val hypervisor = SimHypervisor(engine, FlowMultiplexerFactory.maxMinMultiplexer(), SplittableRandom(1), PerformanceScalingGovernor()) + SimTrace.ofFragments( + SimTraceFragment(0, duration * 1000, 28.0, 1), + SimTraceFragment(duration * 1000, duration * 1000, 3500.0, 1), + SimTraceFragment(duration * 2000, duration * 1000, 0.0, 1), + SimTraceFragment(duration * 3000, duration * 1000, 183.0, 1) + ).createWorkload(0) - launch { - machine.runWorkload(hypervisor) - println("Hypervisor finished") - } + val engine = FlowEngine.create(coroutineContext, clock) + val graph = engine.newGraph() + + val machine = SimBareMetalMachine.create(graph, model) + val hypervisor = SimHypervisor.create(FlowMultiplexerFactory.maxMinMultiplexer(), SplittableRandom(0L), ScalingGovernors.performance()) + + launch { machine.runWorkload(hypervisor) } yield() val vm = hypervisor.newMachine(model) @@ -110,31 +104,27 @@ internal class SimFairShareHypervisorTest { fun testOvercommittedDual() = runSimulation { val duration = 5 * 60L val workloadA = - SimTraceWorkload( - SimTrace.ofFragments( - SimTraceFragment(0, duration * 1000, 28.0, 1), - SimTraceFragment(duration * 1000, duration * 1000, 3500.0, 1), - SimTraceFragment(duration * 2000, duration * 1000, 0.0, 1), - SimTraceFragment(duration * 3000, duration * 1000, 183.0, 1) - ) - ) + SimTrace.ofFragments( + SimTraceFragment(0, duration * 1000, 28.0, 1), + SimTraceFragment(duration * 1000, duration * 1000, 3500.0, 1), + SimTraceFragment(duration * 2000, duration * 1000, 0.0, 1), + SimTraceFragment(duration * 3000, duration * 1000, 183.0, 1) + ).createWorkload(0) val workloadB = - SimTraceWorkload( - SimTrace.ofFragments( - SimTraceFragment(0, duration * 1000, 28.0, 1), - SimTraceFragment(duration * 1000, duration * 1000, 3100.0, 1), - SimTraceFragment(duration * 2000, duration * 1000, 0.0, 1), - SimTraceFragment(duration * 3000, duration * 1000, 73.0, 1) - ) - ) - - val engine = FlowEngine(coroutineContext, clock) - val machine = SimBareMetalMachine(engine, model, SimplePowerDriver(ConstantPowerModel(0.0))) - val hypervisor = SimHypervisor(engine, FlowMultiplexerFactory.maxMinMultiplexer(), SplittableRandom(1), null) + SimTrace.ofFragments( + SimTraceFragment(0, duration * 1000, 28.0, 1), + SimTraceFragment(duration * 1000, duration * 1000, 3100.0, 1), + SimTraceFragment(duration * 2000, duration * 1000, 0.0, 1), + SimTraceFragment(duration * 3000, duration * 1000, 73.0, 1) + ).createWorkload(0) - launch { - machine.runWorkload(hypervisor) - } + val engine = FlowEngine.create(coroutineContext, clock) + val graph = engine.newGraph() + + val machine = SimBareMetalMachine.create(graph, model) + val hypervisor = SimHypervisor.create(FlowMultiplexerFactory.maxMinMultiplexer(), SplittableRandom(0L), ScalingGovernors.performance()) + + launch { machine.runWorkload(hypervisor) } yield() coroutineScope { @@ -163,18 +153,18 @@ internal class SimFairShareHypervisorTest { fun testMultipleCPUs() = runSimulation { val cpuNode = ProcessingNode("Intel", "Xeon", "amd64", 2) val model = MachineModel( - cpus = List(cpuNode.coreCount) { ProcessingUnit(cpuNode, it, 3200.0) }, - memory = List(4) { MemoryUnit("Crucial", "MTA18ASF4G72AZ-3G2B1", 3200.0, 32_000) } + /*cpus*/ List(cpuNode.coreCount) { ProcessingUnit(cpuNode, it, 3200.0) }, + /*memory*/ List(4) { MemoryUnit("Crucial", "MTA18ASF4G72AZ-3G2B1", 3200.0, 32_000) } ) - val engine = FlowEngine(coroutineContext, clock) - val machine = SimBareMetalMachine(engine, model, SimplePowerDriver(ConstantPowerModel(0.0))) - val hypervisor = SimHypervisor(engine, FlowMultiplexerFactory.maxMinMultiplexer(), SplittableRandom(1), null) + val engine = FlowEngine.create(coroutineContext, clock) + val graph = engine.newGraph() + + val machine = SimBareMetalMachine.create(graph, model) + val hypervisor = SimHypervisor.create(FlowMultiplexerFactory.maxMinMultiplexer(), SplittableRandom(0L), ScalingGovernors.performance()) assertDoesNotThrow { - launch { - machine.runWorkload(hypervisor) - } + launch { machine.runWorkload(hypervisor) } } machine.cancel() @@ -184,39 +174,37 @@ internal class SimFairShareHypervisorTest { fun testInterference() = runSimulation { val cpuNode = ProcessingNode("Intel", "Xeon", "amd64", 2) val model = MachineModel( - cpus = List(cpuNode.coreCount) { ProcessingUnit(cpuNode, it, 3200.0) }, - memory = List(4) { MemoryUnit("Crucial", "MTA18ASF4G72AZ-3G2B1", 3200.0, 32_000) } + /*cpus*/ List(cpuNode.coreCount) { ProcessingUnit(cpuNode, it, 3200.0) }, + /*memory*/ List(4) { MemoryUnit("Crucial", "MTA18ASF4G72AZ-3G2B1", 3200.0, 32_000) } ) val interferenceModel = VmInterferenceModel.builder() - .addGroup(targetLoad = 0.0, score = 0.9, members = setOf("a", "b")) - .addGroup(targetLoad = 0.0, score = 0.6, members = setOf("a", "c")) - .addGroup(targetLoad = 0.1, score = 0.8, members = setOf("a", "n")) + .addGroup(setOf("a", "b"), 0.0, 0.9) + .addGroup(setOf("a", "c"), 0.0, 0.6) + .addGroup(setOf("a", "n"), 0.1, 0.8) .build() - val engine = FlowEngine(coroutineContext, clock) - val machine = SimBareMetalMachine(engine, model, SimplePowerDriver(ConstantPowerModel(0.0))) - val hypervisor = SimHypervisor(engine, FlowMultiplexerFactory.maxMinMultiplexer(), SplittableRandom(1), null) + val engine = FlowEngine.create(coroutineContext, clock) + val graph = engine.newGraph() + + val machine = SimBareMetalMachine.create(graph, model) + val hypervisor = SimHypervisor.create(FlowMultiplexerFactory.maxMinMultiplexer(), SplittableRandom(0L)) val duration = 5 * 60L val workloadA = - SimTraceWorkload( - SimTrace.ofFragments( - SimTraceFragment(0, duration * 1000, 0.0, 1), - SimTraceFragment(duration * 1000, duration * 1000, 28.0, 1), - SimTraceFragment(duration * 2000, duration * 1000, 3500.0, 1), - SimTraceFragment(duration * 3000, duration * 1000, 183.0, 1) - ) - ) + SimTrace.ofFragments( + SimTraceFragment(0, duration * 1000, 0.0, 1), + SimTraceFragment(duration * 1000, duration * 1000, 28.0, 1), + SimTraceFragment(duration * 2000, duration * 1000, 3500.0, 1), + SimTraceFragment(duration * 3000, duration * 1000, 183.0, 1) + ).createWorkload(0) val workloadB = - SimTraceWorkload( - SimTrace.ofFragments( - SimTraceFragment(0, duration * 1000, 0.0, 1), - SimTraceFragment(duration * 1000, duration * 1000, 28.0, 1), - SimTraceFragment(duration * 2000, duration * 1000, 3100.0, 1), - SimTraceFragment(duration * 3000, duration * 1000, 73.0, 1) - ) - ) + SimTrace.ofFragments( + SimTraceFragment(0, duration * 1000, 0.0, 1), + SimTraceFragment(duration * 1000, duration * 1000, 28.0, 1), + SimTraceFragment(duration * 2000, duration * 1000, 3100.0, 1), + SimTraceFragment(duration * 3000, duration * 1000, 73.0, 1) + ).createWorkload(0) launch { machine.runWorkload(hypervisor) diff --git a/opendc-simulator/opendc-simulator-compute/src/test/kotlin/org/opendc/simulator/compute/kernel/SimSpaceSharedHypervisorTest.kt b/opendc-simulator/opendc-simulator-compute/src/test/kotlin/org/opendc/simulator/compute/kernel/SimSpaceSharedHypervisorTest.kt index 57fe3766..ba5a5c68 100644 --- a/opendc-simulator/opendc-simulator-compute/src/test/kotlin/org/opendc/simulator/compute/kernel/SimSpaceSharedHypervisorTest.kt +++ b/opendc-simulator/opendc-simulator-compute/src/test/kotlin/org/opendc/simulator/compute/kernel/SimSpaceSharedHypervisorTest.kt @@ -37,16 +37,13 @@ import org.opendc.simulator.compute.model.MachineModel import org.opendc.simulator.compute.model.MemoryUnit import org.opendc.simulator.compute.model.ProcessingNode import org.opendc.simulator.compute.model.ProcessingUnit -import org.opendc.simulator.compute.power.ConstantPowerModel -import org.opendc.simulator.compute.power.SimplePowerDriver import org.opendc.simulator.compute.runWorkload import org.opendc.simulator.compute.workload.SimFlopsWorkload import org.opendc.simulator.compute.workload.SimRuntimeWorkload import org.opendc.simulator.compute.workload.SimTrace import org.opendc.simulator.compute.workload.SimTraceFragment -import org.opendc.simulator.compute.workload.SimTraceWorkload -import org.opendc.simulator.flow.FlowEngine -import org.opendc.simulator.flow.mux.FlowMultiplexerFactory +import org.opendc.simulator.flow2.FlowEngine +import org.opendc.simulator.flow2.mux.FlowMultiplexerFactory import org.opendc.simulator.kotlin.runSimulation import java.util.SplittableRandom @@ -60,8 +57,8 @@ internal class SimSpaceSharedHypervisorTest { fun setUp() { val cpuNode = ProcessingNode("Intel", "Xeon", "amd64", 1) machineModel = MachineModel( - cpus = List(cpuNode.coreCount) { ProcessingUnit(cpuNode, it, 3200.0) }, - memory = List(4) { MemoryUnit("Crucial", "MTA18ASF4G72AZ-3G2B1", 3200.0, 32_000) } + /*cpus*/ List(cpuNode.coreCount) { ProcessingUnit(cpuNode, it, 3200.0) }, + /*memory*/ List(4) { MemoryUnit("Crucial", "MTA18ASF4G72AZ-3G2B1", 3200.0, 32_000) } ) } @@ -72,18 +69,18 @@ internal class SimSpaceSharedHypervisorTest { fun testTrace() = runSimulation { val duration = 5 * 60L val workloadA = - SimTraceWorkload( - SimTrace.ofFragments( - SimTraceFragment(0, duration * 1000, 28.0, 1), - SimTraceFragment(duration * 1000, duration * 1000, 3500.0, 1), - SimTraceFragment(duration * 2000, duration * 1000, 0.0, 1), - SimTraceFragment(duration * 3000, duration * 1000, 183.0, 1) - ) - ) - - val engine = FlowEngine(coroutineContext, clock) - val machine = SimBareMetalMachine(engine, machineModel, SimplePowerDriver(ConstantPowerModel(0.0))) - val hypervisor = SimHypervisor(engine, FlowMultiplexerFactory.forwardingMultiplexer(), SplittableRandom(1), null) + SimTrace.ofFragments( + SimTraceFragment(0, duration * 1000, 28.0, 1), + SimTraceFragment(duration * 1000, duration * 1000, 3500.0, 1), + SimTraceFragment(duration * 2000, duration * 1000, 0.0, 1), + SimTraceFragment(duration * 3000, duration * 1000, 183.0, 1) + ).createWorkload(0) + + val engine = FlowEngine.create(coroutineContext, clock) + val graph = engine.newGraph() + + val machine = SimBareMetalMachine.create(graph, machineModel) + val hypervisor = SimHypervisor.create(FlowMultiplexerFactory.forwardingMultiplexer(), SplittableRandom(0L)) launch { machine.runWorkload(hypervisor) } val vm = hypervisor.newMachine(machineModel) @@ -102,10 +99,12 @@ internal class SimSpaceSharedHypervisorTest { @Test fun testRuntimeWorkload() = runSimulation { val duration = 5 * 60L * 1000 - val workload = SimRuntimeWorkload(duration) - val engine = FlowEngine(coroutineContext, clock) - val machine = SimBareMetalMachine(engine, machineModel, SimplePowerDriver(ConstantPowerModel(0.0))) - val hypervisor = SimHypervisor(engine, FlowMultiplexerFactory.forwardingMultiplexer(), SplittableRandom(1), null) + val workload = SimRuntimeWorkload(duration, 1.0) + val engine = FlowEngine.create(coroutineContext, clock) + val graph = engine.newGraph() + + val machine = SimBareMetalMachine.create(graph, machineModel) + val hypervisor = SimHypervisor.create(FlowMultiplexerFactory.forwardingMultiplexer(), SplittableRandom(0L)) launch { machine.runWorkload(hypervisor) } yield() @@ -125,9 +124,11 @@ internal class SimSpaceSharedHypervisorTest { fun testFlopsWorkload() = runSimulation { val duration = 5 * 60L * 1000 val workload = SimFlopsWorkload((duration * 3.2).toLong(), 1.0) - val engine = FlowEngine(coroutineContext, clock) - val machine = SimBareMetalMachine(engine, machineModel, SimplePowerDriver(ConstantPowerModel(0.0))) - val hypervisor = SimHypervisor(engine, FlowMultiplexerFactory.forwardingMultiplexer(), SplittableRandom(1), null) + val engine = FlowEngine.create(coroutineContext, clock) + val graph = engine.newGraph() + + val machine = SimBareMetalMachine.create(graph, machineModel) + val hypervisor = SimHypervisor.create(FlowMultiplexerFactory.forwardingMultiplexer(), SplittableRandom(0L)) launch { machine.runWorkload(hypervisor) } yield() @@ -144,21 +145,23 @@ internal class SimSpaceSharedHypervisorTest { @Test fun testTwoWorkloads() = runSimulation { val duration = 5 * 60L * 1000 - val engine = FlowEngine(coroutineContext, clock) - val machine = SimBareMetalMachine(engine, machineModel, SimplePowerDriver(ConstantPowerModel(0.0))) - val hypervisor = SimHypervisor(engine, FlowMultiplexerFactory.forwardingMultiplexer(), SplittableRandom(1), null) + val engine = FlowEngine.create(coroutineContext, clock) + val graph = engine.newGraph() + + val machine = SimBareMetalMachine.create(graph, machineModel) + val hypervisor = SimHypervisor.create(FlowMultiplexerFactory.forwardingMultiplexer(), SplittableRandom(0L)) launch { machine.runWorkload(hypervisor) } yield() val vm = hypervisor.newMachine(machineModel) - vm.runWorkload(SimRuntimeWorkload(duration)) + vm.runWorkload(SimRuntimeWorkload(duration, 1.0)) hypervisor.removeMachine(vm) yield() val vm2 = hypervisor.newMachine(machineModel) - vm2.runWorkload(SimRuntimeWorkload(duration)) + vm2.runWorkload(SimRuntimeWorkload(duration, 1.0)) hypervisor.removeMachine(vm2) machine.cancel() @@ -171,14 +174,18 @@ internal class SimSpaceSharedHypervisorTest { */ @Test fun testConcurrentWorkloadFails() = runSimulation { - val engine = FlowEngine(coroutineContext, clock) - val machine = SimBareMetalMachine(engine, machineModel, SimplePowerDriver(ConstantPowerModel(0.0))) - val hypervisor = SimHypervisor(engine, FlowMultiplexerFactory.forwardingMultiplexer(), SplittableRandom(1), null) + val engine = FlowEngine.create(coroutineContext, clock) + val graph = engine.newGraph() + + val machine = SimBareMetalMachine.create(graph, machineModel) + val hypervisor = SimHypervisor.create(FlowMultiplexerFactory.forwardingMultiplexer(), SplittableRandom(0L)) launch { machine.runWorkload(hypervisor) } yield() - hypervisor.newMachine(machineModel) + val vm = hypervisor.newMachine(machineModel) + launch { vm.runWorkload(SimFlopsWorkload(10_000, 1.0)) } + yield() assertAll( { assertFalse(hypervisor.canFit(machineModel)) }, @@ -186,6 +193,7 @@ internal class SimSpaceSharedHypervisorTest { ) machine.cancel() + vm.cancel() } /** @@ -193,9 +201,11 @@ internal class SimSpaceSharedHypervisorTest { */ @Test fun testConcurrentWorkloadSucceeds() = runSimulation { - val engine = FlowEngine(coroutineContext, clock) - val machine = SimBareMetalMachine(engine, machineModel, SimplePowerDriver(ConstantPowerModel(0.0))) - val hypervisor = SimHypervisor(engine, FlowMultiplexerFactory.forwardingMultiplexer(), SplittableRandom(1), null) + val engine = FlowEngine.create(coroutineContext, clock) + val graph = engine.newGraph() + + val machine = SimBareMetalMachine.create(graph, machineModel) + val hypervisor = SimHypervisor.create(FlowMultiplexerFactory.forwardingMultiplexer(), SplittableRandom(0L)) launch { machine.runWorkload(hypervisor) } yield() diff --git a/opendc-simulator/opendc-simulator-compute/src/test/kotlin/org/opendc/simulator/compute/kernel/cpufreq/ConservativeScalingGovernorTest.kt b/opendc-simulator/opendc-simulator-compute/src/test/kotlin/org/opendc/simulator/compute/kernel/cpufreq/ConservativeScalingGovernorTest.kt index ef354569..6b182f4c 100644 --- a/opendc-simulator/opendc-simulator-compute/src/test/kotlin/org/opendc/simulator/compute/kernel/cpufreq/ConservativeScalingGovernorTest.kt +++ b/opendc-simulator/opendc-simulator-compute/src/test/kotlin/org/opendc/simulator/compute/kernel/cpufreq/ConservativeScalingGovernorTest.kt @@ -25,11 +25,10 @@ package org.opendc.simulator.compute.kernel.cpufreq import io.mockk.every import io.mockk.mockk import io.mockk.verify -import org.junit.jupiter.api.Assertions.assertEquals import org.junit.jupiter.api.Test /** - * Test suite for the [ConservativeScalingGovernor] + * Test suite for the conservative [ScalingGovernor]. */ internal class ConservativeScalingGovernorTest { @Test @@ -38,7 +37,7 @@ internal class ConservativeScalingGovernorTest { val minSpeed = cpuCapacity / 2 val defaultThreshold = 0.8 val defaultStepSize = 0.05 * cpuCapacity - val governor = ConservativeScalingGovernor() + val governor = ScalingGovernors.conservative(defaultThreshold) val policy = mockk<ScalingPolicy>(relaxUnitFun = true) every { policy.max } returns cpuCapacity @@ -48,10 +47,8 @@ internal class ConservativeScalingGovernorTest { every { policy.target } answers { target } every { policy.target = any() } propertyType Double::class answers { target = value } - val logic = governor.createLogic(policy) + val logic = governor.newGovernor(policy) logic.onStart() - assertEquals(defaultThreshold, governor.threshold) - logic.onLimit(0.5) /* Upwards scaling */ @@ -71,7 +68,7 @@ internal class ConservativeScalingGovernorTest { val minSpeed = firstPState val threshold = 0.5 val stepSize = 0.02 * cpuCapacity - val governor = ConservativeScalingGovernor(threshold, stepSize) + val governor = ScalingGovernors.conservative(threshold, stepSize) val policy = mockk<ScalingPolicy>(relaxUnitFun = true) every { policy.max } returns cpuCapacity @@ -81,9 +78,8 @@ internal class ConservativeScalingGovernorTest { every { policy.target } answers { target } every { policy.target = any() } propertyType Double::class answers { target = value } - val logic = governor.createLogic(policy) + val logic = governor.newGovernor(policy) logic.onStart() - assertEquals(threshold, governor.threshold) logic.onLimit(0.5) /* Upwards scaling */ diff --git a/opendc-simulator/opendc-simulator-compute/src/test/kotlin/org/opendc/simulator/compute/kernel/cpufreq/OnDemandScalingGovernorTest.kt b/opendc-simulator/opendc-simulator-compute/src/test/kotlin/org/opendc/simulator/compute/kernel/cpufreq/OnDemandScalingGovernorTest.kt index ca759e39..d6a7090b 100644 --- a/opendc-simulator/opendc-simulator-compute/src/test/kotlin/org/opendc/simulator/compute/kernel/cpufreq/OnDemandScalingGovernorTest.kt +++ b/opendc-simulator/opendc-simulator-compute/src/test/kotlin/org/opendc/simulator/compute/kernel/cpufreq/OnDemandScalingGovernorTest.kt @@ -25,11 +25,10 @@ package org.opendc.simulator.compute.kernel.cpufreq import io.mockk.every import io.mockk.mockk import io.mockk.verify -import org.junit.jupiter.api.Assertions.assertEquals import org.junit.jupiter.api.Test /** - * Test suite for the [OnDemandScalingGovernor] + * Test suite for the on-demand [ScalingGovernor]. */ internal class OnDemandScalingGovernorTest { @Test @@ -37,15 +36,14 @@ internal class OnDemandScalingGovernorTest { val cpuCapacity = 4100.0 val minSpeed = cpuCapacity / 2 val defaultThreshold = 0.8 - val governor = OnDemandScalingGovernor() + val governor = ScalingGovernors.ondemand(defaultThreshold) val policy = mockk<ScalingPolicy>(relaxUnitFun = true) every { policy.min } returns minSpeed every { policy.max } returns cpuCapacity - val logic = governor.createLogic(policy) + val logic = governor.newGovernor(policy) logic.onStart() - assertEquals(defaultThreshold, governor.threshold) verify(exactly = 1) { policy.target = minSpeed } logic.onLimit(0.5) @@ -60,16 +58,15 @@ internal class OnDemandScalingGovernorTest { val firstPState = 1000.0 val cpuCapacity = 4100.0 val threshold = 0.5 - val governor = OnDemandScalingGovernor(threshold) + val governor = ScalingGovernors.ondemand(threshold) val policy = mockk<ScalingPolicy>(relaxUnitFun = true) every { policy.max } returns cpuCapacity every { policy.min } returns firstPState - val logic = governor.createLogic(policy) + val logic = governor.newGovernor(policy) logic.onStart() - assertEquals(threshold, governor.threshold) verify(exactly = 1) { policy.target = firstPState } logic.onLimit(0.1) diff --git a/opendc-simulator/opendc-simulator-compute/src/test/kotlin/org/opendc/simulator/compute/kernel/cpufreq/PerformanceScalingGovernorTest.kt b/opendc-simulator/opendc-simulator-compute/src/test/kotlin/org/opendc/simulator/compute/kernel/cpufreq/PerformanceScalingGovernorTest.kt index a4bb24f2..f03f41fe 100644 --- a/opendc-simulator/opendc-simulator-compute/src/test/kotlin/org/opendc/simulator/compute/kernel/cpufreq/PerformanceScalingGovernorTest.kt +++ b/opendc-simulator/opendc-simulator-compute/src/test/kotlin/org/opendc/simulator/compute/kernel/cpufreq/PerformanceScalingGovernorTest.kt @@ -34,7 +34,7 @@ internal class PerformanceScalingGovernorTest { @Test fun testSetStartLimit() { val policy = spyk<ScalingPolicy>() - val logic = PerformanceScalingGovernor().createLogic(policy) + val logic = ScalingGovernors.performance().newGovernor(policy) every { policy.max } returns 4100.0 diff --git a/opendc-simulator/opendc-simulator-compute/src/test/kotlin/org/opendc/simulator/compute/kernel/cpufreq/PowerSaveScalingGovernorTest.kt b/opendc-simulator/opendc-simulator-compute/src/test/kotlin/org/opendc/simulator/compute/kernel/cpufreq/PowerSaveScalingGovernorTest.kt index 662d55fb..4cee8199 100644 --- a/opendc-simulator/opendc-simulator-compute/src/test/kotlin/org/opendc/simulator/compute/kernel/cpufreq/PowerSaveScalingGovernorTest.kt +++ b/opendc-simulator/opendc-simulator-compute/src/test/kotlin/org/opendc/simulator/compute/kernel/cpufreq/PowerSaveScalingGovernorTest.kt @@ -36,7 +36,7 @@ internal class PowerSaveScalingGovernorTest { val cpuCapacity = 4100.0 val minSpeed = cpuCapacity / 2 val policy = mockk<ScalingPolicy>(relaxUnitFun = true) - val logic = PowerSaveScalingGovernor().createLogic(policy) + val logic = ScalingGovernors.powerSave().newGovernor(policy) every { policy.max } returns cpuCapacity every { policy.min } returns minSpeed @@ -55,7 +55,7 @@ internal class PowerSaveScalingGovernorTest { val cpuCapacity = 4100.0 val firstPState = 1000.0 val policy = mockk<ScalingPolicy>(relaxUnitFun = true) - val logic = PowerSaveScalingGovernor().createLogic(policy) + val logic = ScalingGovernors.powerSave().newGovernor(policy) every { policy.max } returns cpuCapacity every { policy.min } returns firstPState diff --git a/opendc-simulator/opendc-simulator-compute/src/test/kotlin/org/opendc/simulator/compute/power/PStatePowerDriverTest.kt b/opendc-simulator/opendc-simulator-compute/src/test/kotlin/org/opendc/simulator/compute/power/PStatePowerDriverTest.kt deleted file mode 100644 index 3c0a55a6..00000000 --- a/opendc-simulator/opendc-simulator-compute/src/test/kotlin/org/opendc/simulator/compute/power/PStatePowerDriverTest.kt +++ /dev/null @@ -1,123 +0,0 @@ -/* - * Copyright (c) 2021 AtLarge Research - * - * Permission is hereby granted, free of charge, to any person obtaining a copy - * of this software and associated documentation files (the "Software"), to deal - * in the Software without restriction, including without limitation the rights - * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell - * copies of the Software, and to permit persons to whom the Software is - * furnished to do so, subject to the following conditions: - * - * The above copyright notice and this permission notice shall be included in all - * copies or substantial portions of the Software. - * - * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR - * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, - * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE - * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER - * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, - * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE - * SOFTWARE. - */ - -package org.opendc.simulator.compute.power - -import io.mockk.every -import io.mockk.mockk -import org.junit.jupiter.api.Assertions.assertEquals -import org.junit.jupiter.api.Test -import org.opendc.simulator.compute.SimBareMetalMachine -import org.opendc.simulator.compute.SimProcessingUnit - -/** - * Test suite for [PStatePowerDriver]. - */ -internal class PStatePowerDriverTest { - @Test - fun testPowerBaseline() { - val machine = mockk<SimBareMetalMachine>() - - val driver = PStatePowerDriver( - sortedMapOf( - 2800.0 to ConstantPowerModel(200.0), - 3300.0 to ConstantPowerModel(300.0), - 3600.0 to ConstantPowerModel(350.0) - ) - ) - - val logic = driver.createLogic(machine, emptyList()) - assertEquals(200.0, logic.computePower()) - } - - @Test - fun testPowerWithSingleCpu() { - val machine = mockk<SimBareMetalMachine>() - val cpu = mockk<SimProcessingUnit>(relaxUnitFun = true) - - every { cpu.capacity } returns 3200.0 - every { cpu.rate } returns 1200.0 - - val driver = PStatePowerDriver( - sortedMapOf( - 2800.0 to ConstantPowerModel(200.0), - 3300.0 to ConstantPowerModel(300.0), - 3600.0 to ConstantPowerModel(350.0) - ) - ) - - val logic = driver.createLogic(machine, listOf(cpu)) - - assertEquals(300.0, logic.computePower()) - } - - @Test - fun testPowerWithMultipleCpus() { - val machine = mockk<SimBareMetalMachine>() - val cpu = mockk<SimProcessingUnit>(relaxUnitFun = true) - val cpus = listOf(cpu, cpu) - - every { cpus[0].capacity } returns 1000.0 - every { cpus[0].rate } returns 1200.0 - - every { cpus[1].capacity } returns 3500.0 - every { cpus[1].rate } returns 1200.0 - - val driver = PStatePowerDriver( - sortedMapOf( - 2800.0 to ConstantPowerModel(200.0), - 3300.0 to ConstantPowerModel(300.0), - 3600.0 to ConstantPowerModel(350.0) - ) - ) - - val logic = driver.createLogic(machine, cpus) - - assertEquals(350.0, logic.computePower()) - } - - @Test - fun testPowerBasedOnUtilization() { - val machine = mockk<SimBareMetalMachine>() - val cpu = mockk<SimProcessingUnit>(relaxUnitFun = true) - - every { cpu.model.frequency } returns 4200.0 - - val driver = PStatePowerDriver( - sortedMapOf( - 2800.0 to LinearPowerModel(200.0, 100.0), - 3300.0 to LinearPowerModel(250.0, 150.0), - 4000.0 to LinearPowerModel(300.0, 200.0) - ) - ) - - val logic = driver.createLogic(machine, listOf(cpu)) - - every { cpu.rate } returns 1400.0 - every { cpu.capacity } returns 1400.0 - assertEquals(150.0, logic.computePower()) - - every { cpu.rate } returns 1400.0 - every { cpu.capacity } returns 4000.0 - assertEquals(235.0, logic.computePower()) - } -} diff --git a/opendc-simulator/opendc-simulator-compute/src/test/kotlin/org/opendc/simulator/compute/power/PowerModelTest.kt b/opendc-simulator/opendc-simulator-compute/src/test/kotlin/org/opendc/simulator/compute/power/PowerModelTest.kt index 67532d5b..9a6263c5 100644 --- a/opendc-simulator/opendc-simulator-compute/src/test/kotlin/org/opendc/simulator/compute/power/PowerModelTest.kt +++ b/opendc-simulator/opendc-simulator-compute/src/test/kotlin/org/opendc/simulator/compute/power/PowerModelTest.kt @@ -38,7 +38,7 @@ internal class PowerModelTest { @ParameterizedTest @MethodSource("MachinePowerModelArgs") fun `compute power consumption given CPU loads`( - powerModel: PowerModel, + powerModel: CpuPowerModel, expectedPowerConsumption: Double ) { val computedPowerConsumption = powerModel.computePower(cpuUtil) @@ -48,10 +48,10 @@ internal class PowerModelTest { @ParameterizedTest @MethodSource("MachinePowerModelArgs") fun `ignore idle power when computing power consumptions`( - powerModel: PowerModel, + powerModel: CpuPowerModel, expectedPowerConsumption: Double ) { - val zeroPowerModel = ZeroIdlePowerDecorator(powerModel) + val zeroPowerModel = CpuPowerModels.zeroIdle(powerModel) assertAll( { assertEquals(expectedPowerConsumption, zeroPowerModel.computePower(cpuUtil), epsilon) }, @@ -61,8 +61,9 @@ internal class PowerModelTest { @Test fun `compute power draw by the SPEC benchmark model`() { - val ibm = listOf(58.4, 98.0, 109.0, 118.0, 128.0, 140.0, 153.0, 170.0, 189.0, 205.0, 222.0) - val powerModel = InterpolationPowerModel(ibm) + val powerModel = CpuPowerModels.interpolate( + 58.4, 98.0, 109.0, 118.0, 128.0, 140.0, 153.0, 170.0, 189.0, 205.0, 222.0 + ) assertAll( { assertEquals(58.4, powerModel.computePower(0.0)) }, @@ -80,14 +81,14 @@ internal class PowerModelTest { private companion object { @JvmStatic fun MachinePowerModelArgs(): Stream<Arguments> = Stream.of( - Arguments.of(ConstantPowerModel(0.0), 0.0), - Arguments.of(LinearPowerModel(350.0, 200.0), 335.0), - Arguments.of(SquarePowerModel(350.0, 200.0), 321.5), - Arguments.of(CubicPowerModel(350.0, 200.0), 309.35), - Arguments.of(SqrtPowerModel(350.0, 200.0), 342.302), - Arguments.of(MsePowerModel(350.0, 200.0, 1.4), 340.571), - Arguments.of(AsymptoticPowerModel(350.0, 200.0, 0.3, false), 338.765), - Arguments.of(AsymptoticPowerModel(350.0, 200.0, 0.3, true), 323.072) + Arguments.of(CpuPowerModels.constant(0.0), 0.0), + Arguments.of(CpuPowerModels.linear(350.0, 200.0), 335.0), + Arguments.of(CpuPowerModels.square(350.0, 200.0), 321.5), + Arguments.of(CpuPowerModels.cubic(350.0, 200.0), 309.35), + Arguments.of(CpuPowerModels.sqrt(350.0, 200.0), 342.302), + Arguments.of(CpuPowerModels.mse(350.0, 200.0, 1.4), 340.571), + Arguments.of(CpuPowerModels.asymptotic(350.0, 200.0, 0.3, false), 338.765), + Arguments.of(CpuPowerModels.asymptotic(350.0, 200.0, 0.3, true), 323.072) ) } } diff --git a/opendc-simulator/opendc-simulator-compute/src/test/kotlin/org/opendc/simulator/compute/workload/SimFlopsWorkloadTest.kt b/opendc-simulator/opendc-simulator-compute/src/test/kotlin/org/opendc/simulator/compute/workload/SimFlopsWorkloadTest.kt index b3e57453..edbc0571 100644 --- a/opendc-simulator/opendc-simulator-compute/src/test/kotlin/org/opendc/simulator/compute/workload/SimFlopsWorkloadTest.kt +++ b/opendc-simulator/opendc-simulator-compute/src/test/kotlin/org/opendc/simulator/compute/workload/SimFlopsWorkloadTest.kt @@ -32,7 +32,7 @@ class SimFlopsWorkloadTest { @Test fun testFlopsNonNegative() { assertThrows<IllegalArgumentException>("FLOPs must be non-negative") { - SimFlopsWorkload(-1) + SimFlopsWorkload(-1, 1.0) } } diff --git a/opendc-simulator/opendc-simulator-compute/src/test/kotlin/org/opendc/simulator/compute/workload/SimTraceWorkloadTest.kt b/opendc-simulator/opendc-simulator-compute/src/test/kotlin/org/opendc/simulator/compute/workload/SimTraceWorkloadTest.kt index 83e1f81c..e3b6e6c5 100644 --- a/opendc-simulator/opendc-simulator-compute/src/test/kotlin/org/opendc/simulator/compute/workload/SimTraceWorkloadTest.kt +++ b/opendc-simulator/opendc-simulator-compute/src/test/kotlin/org/opendc/simulator/compute/workload/SimTraceWorkloadTest.kt @@ -31,10 +31,8 @@ import org.opendc.simulator.compute.model.MachineModel import org.opendc.simulator.compute.model.MemoryUnit import org.opendc.simulator.compute.model.ProcessingNode import org.opendc.simulator.compute.model.ProcessingUnit -import org.opendc.simulator.compute.power.ConstantPowerModel -import org.opendc.simulator.compute.power.SimplePowerDriver import org.opendc.simulator.compute.runWorkload -import org.opendc.simulator.flow.FlowEngine +import org.opendc.simulator.flow2.FlowEngine import org.opendc.simulator.kotlin.runSimulation /** @@ -48,28 +46,28 @@ class SimTraceWorkloadTest { val cpuNode = ProcessingNode("Intel", "Xeon", "amd64", 2) machineModel = MachineModel( - cpus = List(cpuNode.coreCount) { ProcessingUnit(cpuNode, it, 1000.0) }, - memory = List(4) { MemoryUnit("Crucial", "MTA18ASF4G72AZ-3G2B1", 3200.0, 32_000) } + /*cpus*/ List(cpuNode.coreCount) { ProcessingUnit(cpuNode, it, 1000.0) }, + /*memory*/ List(4) { MemoryUnit("Crucial", "MTA18ASF4G72AZ-3G2B1", 3200.0, 32_000) } ) } @Test fun testSmoke() = runSimulation { - val machine = SimBareMetalMachine( - FlowEngine(coroutineContext, clock), - machineModel, - SimplePowerDriver(ConstantPowerModel(0.0)) + val engine = FlowEngine.create(coroutineContext, clock) + val graph = engine.newGraph() + + val machine = SimBareMetalMachine.create( + graph, + machineModel ) - val workload = SimTraceWorkload( + val workload = SimTrace.ofFragments( SimTraceFragment(0, 1000, 2 * 28.0, 2), SimTraceFragment(1000, 1000, 2 * 3100.0, 2), SimTraceFragment(2000, 1000, 0.0, 2), SimTraceFragment(3000, 1000, 2 * 73.0, 2) - ), - offset = 0 - ) + ).createWorkload(0) machine.runWorkload(workload) @@ -78,21 +76,21 @@ class SimTraceWorkloadTest { @Test fun testOffset() = runSimulation { - val machine = SimBareMetalMachine( - FlowEngine(coroutineContext, clock), - machineModel, - SimplePowerDriver(ConstantPowerModel(0.0)) + val engine = FlowEngine.create(coroutineContext, clock) + val graph = engine.newGraph() + + val machine = SimBareMetalMachine.create( + graph, + machineModel ) - val workload = SimTraceWorkload( + val workload = SimTrace.ofFragments( SimTraceFragment(0, 1000, 2 * 28.0, 2), SimTraceFragment(1000, 1000, 2 * 3100.0, 2), SimTraceFragment(2000, 1000, 0.0, 2), SimTraceFragment(3000, 1000, 2 * 73.0, 2) - ), - offset = 1000 - ) + ).createWorkload(1000) machine.runWorkload(workload) @@ -101,21 +99,21 @@ class SimTraceWorkloadTest { @Test fun testSkipFragment() = runSimulation { - val machine = SimBareMetalMachine( - FlowEngine(coroutineContext, clock), - machineModel, - SimplePowerDriver(ConstantPowerModel(0.0)) + val engine = FlowEngine.create(coroutineContext, clock) + val graph = engine.newGraph() + + val machine = SimBareMetalMachine.create( + graph, + machineModel ) - val workload = SimTraceWorkload( + val workload = SimTrace.ofFragments( SimTraceFragment(0, 1000, 2 * 28.0, 2), SimTraceFragment(1000, 1000, 2 * 3100.0, 2), SimTraceFragment(2000, 1000, 0.0, 2), SimTraceFragment(3000, 1000, 2 * 73.0, 2) - ), - offset = 0 - ) + ).createWorkload(0) delay(1000L) machine.runWorkload(workload) @@ -125,21 +123,21 @@ class SimTraceWorkloadTest { @Test fun testZeroCores() = runSimulation { - val machine = SimBareMetalMachine( - FlowEngine(coroutineContext, clock), - machineModel, - SimplePowerDriver(ConstantPowerModel(0.0)) + val engine = FlowEngine.create(coroutineContext, clock) + val graph = engine.newGraph() + + val machine = SimBareMetalMachine.create( + graph, + machineModel ) - val workload = SimTraceWorkload( + val workload = SimTrace.ofFragments( SimTraceFragment(0, 1000, 2 * 28.0, 2), SimTraceFragment(1000, 1000, 2 * 3100.0, 2), SimTraceFragment(2000, 1000, 0.0, 0), SimTraceFragment(3000, 1000, 2 * 73.0, 2) - ), - offset = 0 - ) + ).createWorkload(0) machine.runWorkload(workload) diff --git a/opendc-simulator/opendc-simulator-compute/src/test/resources/spec_machines.yml b/opendc-simulator/opendc-simulator-compute/src/test/resources/spec_machines.yml deleted file mode 100644 index d51cba80..00000000 --- a/opendc-simulator/opendc-simulator-compute/src/test/resources/spec_machines.yml +++ /dev/null @@ -1,29 +0,0 @@ ---- -# The power model of an IBM server x3550 (2 x [Xeon X5675 3067 MHz, 6 cores], 16GB).<br/> -# <a href="http://www.spec.org/power_ssj2008/results/res2011q2/power_ssj2008-20110406-00368.html"> -# http://www.spec.org/power_ssj2008/results/res2011q2/power_ssj2008-20110406-00368.html</a> -IBMx3550M3_XeonX5675: [58.4, 98.0, 109.0, 118.0, 128.0, 140.0, 153.0, 170.0, 189.0, 205.0, 222.0] - # The power model of an IBM server x3550 (2 x [Xeon X5670 2933 MHz, 6 cores], 12GB).<br/> - # <a href="http://www.spec.org/power_ssj2008/results/res2010q2/power_ssj2008-20100315-00239.html"> - # http://www.spec.org/power_ssj2008/results/res2010q2/power_ssj2008-20100315-00239.html</a> -IBMx3550M3_XeonX5670: [66.0, 107.0, 120.0, 131.0, 143.0, 156.0, 173.0, 191.0, 211.0, 229.0, 247.0] - # the power model of an IBM server x3250 (1 x [Xeon X3480 3067 MHz, 4 cores], 8GB).<br/> - # <a href="http://www.spec.org/power_ssj2008/results/res2010q4/power_ssj2008-20101001-00297.html"> - # http://www.spec.org/power_ssj2008/results/res2010q4/power_ssj2008-20101001-00297.html</a> -IBMx3250M3_XeonX3480: [42.3, 46.7, 49.7, 55.4, 61.8, 69.3, 76.1, 87.0, 96.1, 106.0, 113.0] - # The power model of an IBM server x3250 (1 x [Xeon X3470 2933 MHz, 4 cores], 8GB).<br/> - # <a href="http://www.spec.org/power_ssj2008/results/res2009q4/power_ssj2008-20091104-00213.html"> - # http://www.spec.org/power_ssj2008/results/res2009q4/power_ssj2008-20091104-00213.html</a> -IBMx3250M3_XeonX3470: [41.6, 46.7, 52.3, 57.9, 65.4, 73.0, 80.7, 89.5, 99.6, 105.0, 113.0] - # The power model of an HP ProLiant ML110 G5 (1 x [Xeon 3075 2660 MHz, 2 cores], 4GB).<br/> - # <a href="http://www.spec.org/power_ssj2008/results/res2011q1/power_ssj2008-20110124-00339.html"> - # http://www.spec.org/power_ssj2008/results/res2011q1/power_ssj2008-20110124-00339.html</a> -HPProLiantMl110G5_Xeon3075: [93.7, 97.0, 101.0, 105.0, 110.0, 116.0, 121.0, 125.0, 129.0, 133.0, 135.0] - # The power model of an HP ProLiant ML110 G4 (1 x [Xeon 3040 1860 MHz, 2 cores], 4GB).<br/> - # <a href="http://www.spec.org/power_ssj2008/results/res2011q1/power_ssj2008-20110127-00342.html"> - # http://www.spec.org/power_ssj2008/results/res2011q1/power_ssj2008-20110127-00342.html</a> -HPProLiantMl110G4_Xeon3040: [86.0, 89.4, 92.6, 96.0, 99.5, 102.0, 106.0, 108.0, 112.0, 114.0, 117.0] - # The power model of an HP ProLiant ML110 G3 (1 x [Pentium D930 3000 MHz, 2 cores], 4GB).<br/> - # <a href="http://www.spec.org/power_ssj2008/results/res2011q1/power_ssj2008-20110127-00342.html"> - # http://www.spec.org/power_ssj2008/results/res2011q1/power_ssj2008-20110127-00342.html</a> -HPProLiantMl110G3_PentiumD930: [105.0, 112.0, 118.0, 125.0, 131.0, 137.0, 147.0, 153.0, 157.0, 164.0, 169.0] diff --git a/opendc-simulator/opendc-simulator-flow/src/jmh/kotlin/org/opendc/simulator/flow/FlowBenchmarks.kt b/opendc-simulator/opendc-simulator-flow/src/jmh/kotlin/org/opendc/simulator/flow/FlowBenchmarks.kt deleted file mode 100644 index 58f84d82..00000000 --- a/opendc-simulator/opendc-simulator-flow/src/jmh/kotlin/org/opendc/simulator/flow/FlowBenchmarks.kt +++ /dev/null @@ -1,137 +0,0 @@ -/* - * 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.flow - -import kotlinx.coroutines.launch -import org.opendc.simulator.flow.mux.ForwardingFlowMultiplexer -import org.opendc.simulator.flow.mux.MaxMinFlowMultiplexer -import org.opendc.simulator.flow.source.TraceFlowSource -import org.opendc.simulator.kotlin.runSimulation -import org.openjdk.jmh.annotations.Benchmark -import org.openjdk.jmh.annotations.Fork -import org.openjdk.jmh.annotations.Measurement -import org.openjdk.jmh.annotations.Scope -import org.openjdk.jmh.annotations.Setup -import org.openjdk.jmh.annotations.State -import org.openjdk.jmh.annotations.Warmup -import java.util.concurrent.ThreadLocalRandom -import java.util.concurrent.TimeUnit - -@State(Scope.Thread) -@Fork(1) -@Warmup(iterations = 2, time = 1, timeUnit = TimeUnit.SECONDS) -@Measurement(iterations = 5, time = 3, timeUnit = TimeUnit.SECONDS) -class FlowBenchmarks { - private lateinit var trace: Sequence<TraceFlowSource.Fragment> - - @Setup - fun setUp() { - val random = ThreadLocalRandom.current() - val entries = List(10000) { TraceFlowSource.Fragment(1000, random.nextDouble(0.0, 4500.0)) } - trace = entries.asSequence() - } - - @Benchmark - fun benchmarkSink() { - return runSimulation { - val engine = FlowEngine(coroutineContext, clock) - val provider = FlowSink(engine, 4200.0) - return@runSimulation provider.consume(TraceFlowSource(trace)) - } - } - - @Benchmark - fun benchmarkForward() { - return runSimulation { - val engine = FlowEngine(coroutineContext, clock) - val provider = FlowSink(engine, 4200.0) - val forwarder = FlowForwarder(engine) - provider.startConsumer(forwarder) - return@runSimulation forwarder.consume(TraceFlowSource(trace)) - } - } - - @Benchmark - fun benchmarkMuxMaxMinSingleSource() { - return runSimulation { - val engine = FlowEngine(coroutineContext, clock) - val switch = MaxMinFlowMultiplexer(engine) - - FlowSink(engine, 3000.0).startConsumer(switch.newOutput()) - FlowSink(engine, 3000.0).startConsumer(switch.newOutput()) - - val provider = switch.newInput() - return@runSimulation provider.consume(TraceFlowSource(trace)) - } - } - - @Benchmark - fun benchmarkMuxMaxMinTripleSource() { - return runSimulation { - val engine = FlowEngine(coroutineContext, clock) - val switch = MaxMinFlowMultiplexer(engine) - - FlowSink(engine, 3000.0).startConsumer(switch.newOutput()) - FlowSink(engine, 3000.0).startConsumer(switch.newOutput()) - - repeat(3) { - launch { - val provider = switch.newInput() - provider.consume(TraceFlowSource(trace)) - } - } - } - } - - @Benchmark - fun benchmarkMuxExclusiveSingleSource() { - return runSimulation { - val engine = FlowEngine(coroutineContext, clock) - val switch = ForwardingFlowMultiplexer(engine) - - FlowSink(engine, 3000.0).startConsumer(switch.newOutput()) - FlowSink(engine, 3000.0).startConsumer(switch.newOutput()) - - val provider = switch.newInput() - return@runSimulation provider.consume(TraceFlowSource(trace)) - } - } - - @Benchmark - fun benchmarkMuxExclusiveTripleSource() { - return runSimulation { - val engine = FlowEngine(coroutineContext, clock) - val switch = ForwardingFlowMultiplexer(engine) - - FlowSink(engine, 3000.0).startConsumer(switch.newOutput()) - FlowSink(engine, 3000.0).startConsumer(switch.newOutput()) - - repeat(2) { - launch { - val provider = switch.newInput() - provider.consume(TraceFlowSource(trace)) - } - } - } - } -} diff --git a/opendc-simulator/opendc-simulator-flow/src/jmh/kotlin/org/opendc/simulator/flow2/FlowBenchmarks.kt b/opendc-simulator/opendc-simulator-flow/src/jmh/kotlin/org/opendc/simulator/flow2/FlowBenchmarks.kt new file mode 100644 index 00000000..fb112082 --- /dev/null +++ b/opendc-simulator/opendc-simulator-flow/src/jmh/kotlin/org/opendc/simulator/flow2/FlowBenchmarks.kt @@ -0,0 +1,124 @@ +/* + * 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.flow2 + +import kotlinx.coroutines.launch +import org.opendc.simulator.flow2.mux.MaxMinFlowMultiplexer +import org.opendc.simulator.flow2.sink.SimpleFlowSink +import org.opendc.simulator.flow2.source.TraceFlowSource +import org.opendc.simulator.flow2.util.FlowTransformer +import org.opendc.simulator.flow2.util.FlowTransforms +import org.opendc.simulator.kotlin.runSimulation +import org.openjdk.jmh.annotations.Benchmark +import org.openjdk.jmh.annotations.Fork +import org.openjdk.jmh.annotations.Measurement +import org.openjdk.jmh.annotations.Scope +import org.openjdk.jmh.annotations.Setup +import org.openjdk.jmh.annotations.State +import org.openjdk.jmh.annotations.Warmup +import java.util.concurrent.ThreadLocalRandom +import java.util.concurrent.TimeUnit + +@State(Scope.Thread) +@Fork(1) +@Warmup(iterations = 2, time = 1, timeUnit = TimeUnit.SECONDS) +@Measurement(iterations = 5, time = 3, timeUnit = TimeUnit.SECONDS) +class FlowBenchmarks { + private lateinit var trace: TraceFlowSource.Trace + + @Setup + fun setUp() { + val random = ThreadLocalRandom.current() + val traceSize = 10_000_000 + trace = TraceFlowSource.Trace( + LongArray(traceSize) { (it + 1) * 1000L }, + FloatArray(traceSize) { random.nextFloat(0.0f, 4500.0f) }, + traceSize + ) + } + + @Benchmark + fun benchmarkSink() { + return runSimulation { + val engine = FlowEngine.create(coroutineContext, clock) + val graph = engine.newGraph() + val sink = SimpleFlowSink(graph, 4200.0f) + val source = TraceFlowSource(graph, trace) + graph.connect(source.output, sink.input) + } + } + + @Benchmark + fun benchmarkForward() { + return runSimulation { + val engine = FlowEngine.create(coroutineContext, clock) + val graph = engine.newGraph() + val sink = SimpleFlowSink(graph, 4200.0f) + val source = TraceFlowSource(graph, trace) + val forwarder = FlowTransformer(graph, FlowTransforms.noop()) + + graph.connect(source.output, forwarder.input) + graph.connect(forwarder.output, sink.input) + } + } + + @Benchmark + fun benchmarkMuxMaxMinSingleSource() { + return runSimulation { + val engine = FlowEngine.create(coroutineContext, clock) + val graph = engine.newGraph() + val switch = MaxMinFlowMultiplexer(graph) + + val sinkA = SimpleFlowSink(graph, 3000.0f) + val sinkB = SimpleFlowSink(graph, 3000.0f) + + graph.connect(switch.newOutput(), sinkA.input) + graph.connect(switch.newOutput(), sinkB.input) + + val source = TraceFlowSource(graph, trace) + graph.connect(source.output, switch.newInput()) + } + } + + @Benchmark + fun benchmarkMuxMaxMinTripleSource() { + return runSimulation { + val engine = FlowEngine.create(coroutineContext, clock) + val graph = engine.newGraph() + val switch = MaxMinFlowMultiplexer(graph) + + val sinkA = SimpleFlowSink(graph, 3000.0f) + val sinkB = SimpleFlowSink(graph, 3000.0f) + + graph.connect(switch.newOutput(), sinkA.input) + graph.connect(switch.newOutput(), sinkB.input) + + repeat(3) { + launch { + val source = TraceFlowSource(graph, trace) + graph.connect(source.output, switch.newInput()) + } + } + } + } +} diff --git a/opendc-simulator/opendc-simulator-flow/src/main/java/org/opendc/simulator/flow2/FlowEngine.java b/opendc-simulator/opendc-simulator-flow/src/main/java/org/opendc/simulator/flow2/FlowEngine.java new file mode 100644 index 00000000..0ebb0da9 --- /dev/null +++ b/opendc-simulator/opendc-simulator-flow/src/main/java/org/opendc/simulator/flow2/FlowEngine.java @@ -0,0 +1,260 @@ +/* + * Copyright (c) 2022 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.flow2; + +import java.time.Clock; +import java.util.ArrayList; +import java.util.List; +import kotlin.coroutines.ContinuationInterceptor; +import kotlin.coroutines.CoroutineContext; +import kotlinx.coroutines.Delay; + +/** + * A {@link FlowEngine} simulates a generic flow network. + * <p> + * The engine centralizes the scheduling logic of state updates of flow connections, allowing update propagation + * to happen more efficiently. and overall, reducing the work necessary to transition into a steady state. + */ +public final class FlowEngine implements Runnable { + /** + * The queue of {@link FlowStage} updates that are scheduled for immediate execution. + */ + private final FlowStageQueue queue = new FlowStageQueue(256); + + /** + * A priority queue containing the {@link FlowStage} updates to be scheduled in the future. + */ + private final FlowTimerQueue timerQueue = new FlowTimerQueue(256); + + /** + * The stack of engine invocations to occur in the future. + */ + private final InvocationStack futureInvocations = new InvocationStack(256); + + /** + * A flag to indicate that the engine is active. + */ + private boolean active; + + private final CoroutineContext coroutineContext; + private final Clock clock; + private final Delay delay; + + /** + * Create a new {@link FlowEngine} instance using the specified {@link CoroutineContext} and {@link Clock}. + */ + public static FlowEngine create(CoroutineContext coroutineContext, Clock clock) { + return new FlowEngine(coroutineContext, clock); + } + + FlowEngine(CoroutineContext coroutineContext, Clock clock) { + this.coroutineContext = coroutineContext; + this.clock = clock; + + CoroutineContext.Key<? extends ContinuationInterceptor> key = ContinuationInterceptor.Key; + this.delay = (Delay) coroutineContext.get(key); + } + + /** + * Obtain the (virtual) {@link Clock} driving the simulation. + */ + public Clock getClock() { + return clock; + } + + /** + * Return a new {@link FlowGraph} that can be used to build a flow network. + */ + public FlowGraph newGraph() { + return new RootGraph(this); + } + + /** + * Enqueue the specified {@link FlowStage} to be updated immediately during the active engine cycle. + * <p> + * This method should be used when the state of a flow context is invalidated/interrupted and needs to be + * re-computed. + */ + void scheduleImmediate(long now, FlowStage ctx) { + scheduleImmediateInContext(ctx); + + // In-case the engine is already running in the call-stack, return immediately. The changes will be picked + // up by the active engine. + if (active) { + return; + } + + trySchedule(futureInvocations, now, now); + } + + /** + * Enqueue the specified {@link FlowStage} to be updated immediately during the active engine cycle. + * <p> + * This method should be used when the state of a flow context is invalidated/interrupted and needs to be + * re-computed. + * <p> + * This method should only be invoked while inside an engine cycle. + */ + void scheduleImmediateInContext(FlowStage ctx) { + queue.add(ctx); + } + + /** + * Enqueue the specified {@link FlowStage} to be updated at its updated deadline. + */ + void scheduleDelayed(FlowStage ctx) { + scheduleDelayedInContext(ctx); + + // In-case the engine is already running in the call-stack, return immediately. The changes will be picked + // up by the active engine. + if (active) { + return; + } + + long deadline = timerQueue.peekDeadline(); + if (deadline != Long.MAX_VALUE) { + trySchedule(futureInvocations, clock.millis(), deadline); + } + } + + /** + * Enqueue the specified {@link FlowStage} to be updated at its updated deadline. + * <p> + * This method should only be invoked while inside an engine cycle. + */ + void scheduleDelayedInContext(FlowStage ctx) { + FlowTimerQueue timerQueue = this.timerQueue; + timerQueue.enqueue(ctx); + } + + /** + * Run all the enqueued actions for the specified timestamp (<code>now</code>). + */ + private void doRunEngine(long now) { + final FlowStageQueue queue = this.queue; + final FlowTimerQueue timerQueue = this.timerQueue; + + try { + // Mark the engine as active to prevent concurrent calls to this method + active = true; + + // Execute all scheduled updates at current timestamp + while (true) { + final FlowStage ctx = timerQueue.poll(now); + if (ctx == null) { + break; + } + + ctx.onUpdate(now); + } + + // Execute all immediate updates + while (true) { + final FlowStage ctx = queue.poll(); + if (ctx == null) { + break; + } + + ctx.onUpdate(now); + } + } finally { + active = false; + } + + // Schedule an engine invocation for the next update to occur. + long headDeadline = timerQueue.peekDeadline(); + if (headDeadline != Long.MAX_VALUE && headDeadline >= now) { + trySchedule(futureInvocations, now, headDeadline); + } + } + + @Override + public void run() { + doRunEngine(futureInvocations.poll()); + } + + /** + * Try to schedule an engine invocation at the specified [target]. + * + * @param scheduled The queue of scheduled invocations. + * @param now The current virtual timestamp. + * @param target The virtual timestamp at which the engine invocation should happen. + */ + private void trySchedule(InvocationStack scheduled, long now, long target) { + // Only schedule a new scheduler invocation in case the target is earlier than all other pending + // scheduler invocations + if (scheduled.tryAdd(target)) { + delay.invokeOnTimeout(target - now, this, coroutineContext); + } + } + + /** + * Internal implementation of a root {@link FlowGraph}. + */ + private static final class RootGraph implements FlowGraphInternal { + private final FlowEngine engine; + private final List<FlowStage> stages = new ArrayList<>(); + + public RootGraph(FlowEngine engine) { + this.engine = engine; + } + + @Override + public FlowEngine getEngine() { + return engine; + } + + @Override + public FlowStage newStage(FlowStageLogic logic) { + final FlowEngine engine = this.engine; + final FlowStage stage = new FlowStage(this, logic); + stages.add(stage); + long now = engine.getClock().millis(); + stage.invalidate(now); + return stage; + } + + @Override + public void connect(Outlet outlet, Inlet inlet) { + FlowGraphInternal.connect(this, outlet, inlet); + } + + @Override + public void disconnect(Outlet outlet) { + FlowGraphInternal.disconnect(this, outlet); + } + + @Override + public void disconnect(Inlet inlet) { + FlowGraphInternal.disconnect(this, inlet); + } + + /** + * Internal method to remove the specified {@link FlowStage} from the graph. + */ + @Override + public void detach(FlowStage stage) { + stages.remove(stage); + } + } +} diff --git a/opendc-simulator/opendc-simulator-flow/src/main/java/org/opendc/simulator/flow2/FlowGraph.java b/opendc-simulator/opendc-simulator-flow/src/main/java/org/opendc/simulator/flow2/FlowGraph.java new file mode 100644 index 00000000..f45be6cd --- /dev/null +++ b/opendc-simulator/opendc-simulator-flow/src/main/java/org/opendc/simulator/flow2/FlowGraph.java @@ -0,0 +1,63 @@ +/* + * Copyright (c) 2022 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.flow2; + +/** + * A representation of a flow network. A flow network is a directed graph where each edge has a capacity and receives an + * amount of flow that cannot exceed the edge's capacity. + */ +public interface FlowGraph { + /** + * Return the {@link FlowEngine} driving the simulation of the graph. + */ + FlowEngine getEngine(); + + /** + * Create a new {@link FlowStage} representing a node in the flow network. + * + * @param logic The logic for handling the events of the stage. + */ + FlowStage newStage(FlowStageLogic logic); + + /** + * Add an edge between the specified outlet port and inlet port in this graph. + * + * @param outlet The outlet of the source from which the flow originates. + * @param inlet The inlet of the sink that should receive the flow. + */ + void connect(Outlet outlet, Inlet inlet); + + /** + * Disconnect the specified {@link Outlet} (if connected). + * + * @param outlet The outlet to disconnect. + */ + void disconnect(Outlet outlet); + + /** + * Disconnect the specified {@link Inlet} (if connected). + * + * @param inlet The inlet to disconnect. + */ + void disconnect(Inlet inlet); +} diff --git a/opendc-simulator/opendc-simulator-flow/src/main/java/org/opendc/simulator/flow2/FlowGraphInternal.java b/opendc-simulator/opendc-simulator-flow/src/main/java/org/opendc/simulator/flow2/FlowGraphInternal.java new file mode 100644 index 00000000..0f608b60 --- /dev/null +++ b/opendc-simulator/opendc-simulator-flow/src/main/java/org/opendc/simulator/flow2/FlowGraphInternal.java @@ -0,0 +1,93 @@ +/* + * Copyright (c) 2022 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.flow2; + +/** + * Interface implemented by {@link FlowGraph} implementations. + */ +interface FlowGraphInternal extends FlowGraph { + /** + * Internal method to remove the specified {@link FlowStage} from the graph. + */ + void detach(FlowStage stage); + + /** + * Helper method to connect an outlet to an inlet. + */ + static void connect(FlowGraph graph, Outlet outlet, Inlet inlet) { + if (!(outlet instanceof OutPort) || !(inlet instanceof InPort)) { + throw new IllegalArgumentException("Invalid outlet or inlet passed to graph"); + } + + InPort inPort = (InPort) inlet; + OutPort outPort = (OutPort) outlet; + + if (!graph.equals(outPort.getGraph()) || !graph.equals(inPort.getGraph())) { + throw new IllegalArgumentException("Outlet or inlet does not belong to graph"); + } else if (outPort.input != null || inPort.output != null) { + throw new IllegalStateException("Inlet or outlet already connected"); + } + + outPort.input = inPort; + inPort.output = outPort; + + inPort.connect(); + outPort.connect(); + } + + /** + * Helper method to disconnect an outlet. + */ + static void disconnect(FlowGraph graph, Outlet outlet) { + if (!(outlet instanceof OutPort)) { + throw new IllegalArgumentException("Invalid outlet passed to graph"); + } + + OutPort outPort = (OutPort) outlet; + + if (!graph.equals(outPort.getGraph())) { + throw new IllegalArgumentException("Outlet or inlet does not belong to graph"); + } + + outPort.cancel(null); + outPort.complete(); + } + + /** + * Helper method to disconnect an inlet. + */ + static void disconnect(FlowGraph graph, Inlet inlet) { + if (!(inlet instanceof InPort)) { + throw new IllegalArgumentException("Invalid outlet passed to graph"); + } + + InPort inPort = (InPort) inlet; + + if (!graph.equals(inPort.getGraph())) { + throw new IllegalArgumentException("Outlet or inlet does not belong to graph"); + } + + inPort.finish(null); + inPort.cancel(null); + } +} diff --git a/opendc-simulator/opendc-simulator-flow/src/main/java/org/opendc/simulator/flow2/FlowStage.java b/opendc-simulator/opendc-simulator-flow/src/main/java/org/opendc/simulator/flow2/FlowStage.java new file mode 100644 index 00000000..4d098043 --- /dev/null +++ b/opendc-simulator/opendc-simulator-flow/src/main/java/org/opendc/simulator/flow2/FlowStage.java @@ -0,0 +1,303 @@ +/* + * Copyright (c) 2022 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.flow2; + +import java.time.Clock; +import java.util.HashMap; +import java.util.Map; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +/** + * A {@link FlowStage} represents a node in a {@link FlowGraph}. + */ +public final class FlowStage { + private static final Logger LOGGER = LoggerFactory.getLogger(FlowStage.class); + + /** + * States of the flow stage. + */ + private static final int STAGE_PENDING = 0; // Stage is pending to be started + + private static final int STAGE_ACTIVE = 1; // Stage is actively running + private static final int STAGE_CLOSED = 2; // Stage is closed + private static final int STAGE_STATE = 0b11; // Mask for accessing the state of the flow stage + + /** + * Flags of the flow connection + */ + private static final int STAGE_INVALIDATE = 1 << 2; // The stage is invalidated + + private static final int STAGE_CLOSE = 1 << 3; // The stage should be closed + private static final int STAGE_UPDATE_ACTIVE = 1 << 4; // An update for the connection is active + private static final int STAGE_UPDATE_PENDING = 1 << 5; // An (immediate) update of the connection is pending + + /** + * The flags representing the state and pending actions for the stage. + */ + private int flags = STAGE_PENDING; + + /** + * The deadline of the stage after which an update should run. + */ + long deadline = Long.MAX_VALUE; + + /** + * The index of the timer in the {@link FlowTimerQueue}. + */ + int timerIndex = -1; + + final Clock clock; + private final FlowStageLogic logic; + final FlowGraphInternal parentGraph; + private final FlowEngine engine; + + private final Map<String, InPort> inlets = new HashMap<>(); + private final Map<String, OutPort> outlets = new HashMap<>(); + private int nextInlet = 0; + private int nextOutlet = 0; + + /** + * Construct a new {@link FlowStage} instance. + * + * @param parentGraph The {@link FlowGraph} this stage belongs to. + * @param logic The logic of the stage. + */ + FlowStage(FlowGraphInternal parentGraph, FlowStageLogic logic) { + this.parentGraph = parentGraph; + this.logic = logic; + this.engine = parentGraph.getEngine(); + this.clock = engine.getClock(); + } + + /** + * Return the {@link FlowGraph} to which this stage belongs. + */ + public FlowGraph getGraph() { + return parentGraph; + } + + /** + * Return the {@link Inlet} (an in-going edge) with the specified <code>name</code> for this {@link FlowStage}. + * If an inlet with that name does not exist, a new one is allocated for the stage. + * + * @param name The name of the inlet. + * @return The {@link InPort} representing an {@link Inlet} with the specified <code>name</code>. + */ + public InPort getInlet(String name) { + return inlets.computeIfAbsent(name, (key) -> new InPort(this, key, nextInlet++)); + } + + /** + * Return the {@link Outlet} (an out-going edge) with the specified <code>name</code> for this {@link FlowStage}. + * If an outlet with that name does not exist, a new one is allocated for the stage. + * + * @param name The name of the outlet. + * @return The {@link OutPort} representing an {@link Outlet} with the specified <code>name</code>. + */ + public OutPort getOutlet(String name) { + return outlets.computeIfAbsent(name, (key) -> new OutPort(this, key, nextOutlet++)); + } + + /** + * Return the current deadline of the {@link FlowStage}'s timer (in milliseconds after epoch). + */ + public long getDeadline() { + return deadline; + } + + /** + * Set the deadline of the {@link FlowStage}'s timer. + * + * @param deadline The new deadline (in milliseconds after epoch) when the stage should be interrupted. + */ + public void setDeadline(long deadline) { + this.deadline = deadline; + + if ((flags & STAGE_UPDATE_ACTIVE) == 0) { + // Update the timer queue with the new deadline + engine.scheduleDelayed(this); + } + } + + /** + * Invalidate the {@link FlowStage} forcing the stage to update. + */ + public void invalidate() { + int flags = this.flags; + + if ((flags & STAGE_UPDATE_ACTIVE) == 0) { + scheduleImmediate(clock.millis(), flags | STAGE_INVALIDATE); + } + } + + /** + * Close the {@link FlowStage} and disconnect all inlets and outlets. + */ + public void close() { + int flags = this.flags; + + if ((flags & STAGE_STATE) == STAGE_CLOSED) { + return; + } + + // Toggle the close bit. In case no update is active, schedule a new update. + if ((flags & STAGE_UPDATE_ACTIVE) != 0) { + this.flags = flags | STAGE_CLOSE; + } else { + scheduleImmediate(clock.millis(), flags | STAGE_CLOSE); + } + } + + /** + * Update the state of the flow stage. + * + * @param now The current virtual timestamp. + */ + void onUpdate(long now) { + int flags = this.flags; + int state = flags & STAGE_STATE; + + if (state == STAGE_ACTIVE) { + doUpdate(now, flags); + } else if (state == STAGE_PENDING) { + doStart(now, flags); + } + } + + /** + * Invalidate the {@link FlowStage} forcing the stage to update. + * + * <p> + * This method is similar to {@link #invalidate()}, but allows the user to manually pass the current timestamp to + * prevent having to re-query the clock. This method should not be called during an update. + */ + void invalidate(long now) { + scheduleImmediate(now, flags | STAGE_INVALIDATE); + } + + /** + * Schedule an immediate update for this stage. + */ + private void scheduleImmediate(long now, int flags) { + // In case an immediate update is already scheduled, no need to do anything + if ((flags & STAGE_UPDATE_PENDING) != 0) { + this.flags = flags; + return; + } + + // Mark the stage that there is an update pending + this.flags = flags | STAGE_UPDATE_PENDING; + + engine.scheduleImmediate(now, this); + } + + /** + * Start the stage. + */ + private void doStart(long now, int flags) { + // Update state before calling into the outside world, so it observes a consistent state + flags = flags | STAGE_ACTIVE | STAGE_UPDATE_ACTIVE; + + doUpdate(now, flags); + } + + /** + * Update the state of the stage. + */ + private void doUpdate(long now, int flags) { + long deadline = this.deadline; + long newDeadline = deadline; + + // Update the stage if: + // (1) the timer of the stage has expired. + // (2) one of the input ports is pushed, + // (3) one of the output ports is pulled, + if ((flags & STAGE_INVALIDATE) != 0 || deadline == now) { + // Update state before calling into the outside world, so it observes a consistent state + this.flags = (flags & ~STAGE_INVALIDATE) | STAGE_UPDATE_ACTIVE; + + try { + newDeadline = logic.onUpdate(this, now); + + // IMPORTANT: Re-fetch the flags after the callback might have changed those + flags = this.flags; + } catch (Exception e) { + doFail(e); + } + } + + // Check whether the stage is marked as closing. + if ((flags & STAGE_CLOSE) != 0) { + doClose(flags, null); + + // IMPORTANT: Re-fetch the flags after the callback might have changed those + flags = this.flags; + } + + // Indicate that no update is active anymore and flush the flags + this.flags = flags & ~(STAGE_UPDATE_ACTIVE | STAGE_UPDATE_PENDING); + this.deadline = newDeadline; + + // Update the timer queue with the new deadline + engine.scheduleDelayedInContext(this); + } + + /** + * This method is invoked when an uncaught exception is caught by the engine. When this happens, the + * {@link FlowStageLogic} "fails" and disconnects all its inputs and outputs. + */ + void doFail(Throwable cause) { + LOGGER.warn("Uncaught exception (closing stage)", cause); + + doClose(flags, cause); + } + + /** + * This method is invoked when the {@link FlowStageLogic} exits successfully or due to failure. + */ + private void doClose(int flags, Throwable cause) { + // Mark the stage as closed + this.flags = flags & ~(STAGE_STATE | STAGE_INVALIDATE | STAGE_CLOSE) | STAGE_CLOSED; + + // Remove stage from parent graph + parentGraph.detach(this); + + // Remove stage from the timer queue + setDeadline(Long.MAX_VALUE); + + // Cancel all input ports + for (InPort port : inlets.values()) { + if (port != null) { + port.cancel(cause); + } + } + + // Cancel all output ports + for (OutPort port : outlets.values()) { + if (port != null) { + port.fail(cause); + } + } + } +} diff --git a/opendc-simulator/opendc-simulator-flow/src/main/java/org/opendc/simulator/flow2/FlowStageLogic.java b/opendc-simulator/opendc-simulator-flow/src/main/java/org/opendc/simulator/flow2/FlowStageLogic.java new file mode 100644 index 00000000..70986a35 --- /dev/null +++ b/opendc-simulator/opendc-simulator-flow/src/main/java/org/opendc/simulator/flow2/FlowStageLogic.java @@ -0,0 +1,38 @@ +/* + * Copyright (c) 2022 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.flow2; + +/** + * The {@link FlowStageLogic} interface is responsible for describing the behaviour of a {@link FlowStage} via + * out-going flows based on its potential inputs. + */ +public interface FlowStageLogic { + /** + * This method is invoked when the one of the stage's inlets or outlets is invalidated. + * + * @param ctx The context in which the stage runs. + * @param now The virtual timestamp in milliseconds after epoch at which the update is occurring. + * @return The next deadline for the stage. + */ + long onUpdate(FlowStage ctx, long now); +} diff --git a/opendc-simulator/opendc-simulator-flow/src/main/java/org/opendc/simulator/flow2/FlowStageQueue.java b/opendc-simulator/opendc-simulator-flow/src/main/java/org/opendc/simulator/flow2/FlowStageQueue.java new file mode 100644 index 00000000..56ec7702 --- /dev/null +++ b/opendc-simulator/opendc-simulator-flow/src/main/java/org/opendc/simulator/flow2/FlowStageQueue.java @@ -0,0 +1,109 @@ +/* + * Copyright (c) 2022 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.flow2; + +import java.util.ArrayDeque; +import java.util.Arrays; + +/** + * A specialized {@link ArrayDeque} implementation that contains the {@link FlowStageLogic}s + * that have been updated during the engine cycle and should converge. + * <p> + * By using a specialized class, we reduce the overhead caused by type-erasure. + */ +final class FlowStageQueue { + /** + * The array of elements in the queue. + */ + private FlowStage[] elements; + + private int head = 0; + private int tail = 0; + + public FlowStageQueue(int initialCapacity) { + elements = new FlowStage[initialCapacity]; + } + + /** + * Add the specified context to the queue. + */ + void add(FlowStage ctx) { + final FlowStage[] es = elements; + int tail = this.tail; + + es[tail] = ctx; + + tail = inc(tail, es.length); + this.tail = tail; + + if (head == tail) { + doubleCapacity(); + } + } + + /** + * Remove a {@link FlowStage} from the queue or <code>null</code> if the queue is empty. + */ + FlowStage poll() { + final FlowStage[] es = elements; + int head = this.head; + FlowStage ctx = es[head]; + + if (ctx != null) { + es[head] = null; + this.head = inc(head, es.length); + } + + return ctx; + } + + /** + * Doubles the capacity of this deque + */ + private void doubleCapacity() { + int oldCapacity = elements.length; + int newCapacity = oldCapacity + (oldCapacity >> 1); + if (newCapacity < 0) { + throw new IllegalStateException("Sorry, deque too big"); + } + + final FlowStage[] es = elements = Arrays.copyOf(elements, newCapacity); + + // Exceptionally, here tail == head needs to be disambiguated + if (tail < head || (tail == head && es[head] != null)) { + // wrap around; slide first leg forward to end of array + int newSpace = newCapacity - oldCapacity; + System.arraycopy(es, head, es, head + newSpace, oldCapacity - head); + for (int i = head, to = (head += newSpace); i < to; i++) es[i] = null; + } + } + + /** + * Circularly increments i, mod modulus. + * Precondition and postcondition: 0 <= i < modulus. + */ + private static int inc(int i, int modulus) { + if (++i >= modulus) i = 0; + return i; + } +} diff --git a/opendc-simulator/opendc-simulator-flow/src/main/java/org/opendc/simulator/flow2/FlowTimerQueue.java b/opendc-simulator/opendc-simulator-flow/src/main/java/org/opendc/simulator/flow2/FlowTimerQueue.java new file mode 100644 index 00000000..4b746202 --- /dev/null +++ b/opendc-simulator/opendc-simulator-flow/src/main/java/org/opendc/simulator/flow2/FlowTimerQueue.java @@ -0,0 +1,208 @@ +/* + * Copyright (c) 2022 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.flow2; + +import java.util.Arrays; + +/** + * A specialized priority queue for timers of {@link FlowStageLogic}s. + * <p> + * By using a specialized priority queue, we reduce the overhead caused by the default priority queue implementation + * being generic. + */ +final class FlowTimerQueue { + /** + * Array representation of binary heap of {@link FlowStage} instances. + */ + private FlowStage[] queue; + + /** + * The number of elements in the priority queue. + */ + private int size = 0; + + /** + * Construct a {@link FlowTimerQueue} with the specified initial capacity. + * + * @param initialCapacity The initial capacity of the queue. + */ + public FlowTimerQueue(int initialCapacity) { + this.queue = new FlowStage[initialCapacity]; + } + + /** + * Enqueue a timer for the specified context or update the existing timer. + */ + void enqueue(FlowStage ctx) { + FlowStage[] es = queue; + int k = ctx.timerIndex; + + if (ctx.deadline != Long.MAX_VALUE) { + if (k >= 0) { + update(es, ctx, k); + } else { + add(es, ctx); + } + } else if (k >= 0) { + delete(es, k); + } + } + + /** + * Retrieve the head of the queue if its deadline does not exceed <code>now</code>. + * + * @param now The timestamp that the deadline of the head of the queue should not exceed. + * @return The head of the queue if its deadline does not exceed <code>now</code>, otherwise <code>null</code>. + */ + FlowStage poll(long now) { + int size = this.size; + if (size == 0) { + return null; + } + + final FlowStage[] es = queue; + final FlowStage head = es[0]; + + if (now < head.deadline) { + return null; + } + + int n = size - 1; + this.size = n; + final FlowStage next = es[n]; + es[n] = null; // Clear the last element of the queue + + if (n > 0) { + siftDown(0, next, es, n); + } + + head.timerIndex = -1; + return head; + } + + /** + * Find the earliest deadline in the queue. + */ + long peekDeadline() { + if (size > 0) { + return queue[0].deadline; + } + + return Long.MAX_VALUE; + } + + /** + * Add a new entry to the queue. + */ + private void add(FlowStage[] es, FlowStage ctx) { + int i = size; + + if (i >= es.length) { + // Re-fetch the resized array + es = grow(); + } + + siftUp(i, ctx, es); + + size = i + 1; + } + + /** + * Update the deadline of an existing entry in the queue. + */ + private void update(FlowStage[] es, FlowStage ctx, int k) { + if (k > 0) { + int parent = (k - 1) >>> 1; + if (es[parent].deadline > ctx.deadline) { + siftUp(k, ctx, es); + return; + } + } + + siftDown(k, ctx, es, size); + } + + /** + * Deadline an entry from the queue. + */ + private void delete(FlowStage[] es, int k) { + int s = --size; + if (s == k) { + es[k] = null; // Element is last in the queue + } else { + FlowStage moved = es[s]; + es[s] = null; + + siftDown(k, moved, es, s); + + if (es[k] == moved) { + siftUp(k, moved, es); + } + } + } + + /** + * Increases the capacity of the array. + */ + private FlowStage[] grow() { + FlowStage[] queue = this.queue; + int oldCapacity = queue.length; + int newCapacity = oldCapacity + (oldCapacity >> 1); + + queue = Arrays.copyOf(queue, newCapacity); + this.queue = queue; + return queue; + } + + private static void siftUp(int k, FlowStage key, FlowStage[] es) { + while (k > 0) { + int parent = (k - 1) >>> 1; + FlowStage e = es[parent]; + if (key.deadline >= e.deadline) break; + es[k] = e; + e.timerIndex = k; + k = parent; + } + es[k] = key; + key.timerIndex = k; + } + + private static void siftDown(int k, FlowStage key, FlowStage[] es, int n) { + int half = n >>> 1; // loop while a non-leaf + while (k < half) { + int child = (k << 1) + 1; // assume left child is least + FlowStage c = es[child]; + int right = child + 1; + if (right < n && c.deadline > es[right].deadline) c = es[child = right]; + + if (key.deadline <= c.deadline) break; + + es[k] = c; + c.timerIndex = k; + k = child; + } + + es[k] = key; + key.timerIndex = k; + } +} diff --git a/opendc-simulator/opendc-simulator-flow/src/main/java/org/opendc/simulator/flow2/InHandler.java b/opendc-simulator/opendc-simulator-flow/src/main/java/org/opendc/simulator/flow2/InHandler.java new file mode 100644 index 00000000..839b01db --- /dev/null +++ b/opendc-simulator/opendc-simulator-flow/src/main/java/org/opendc/simulator/flow2/InHandler.java @@ -0,0 +1,54 @@ +/* + * Copyright (c) 2022 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.flow2; + +/** + * Collection of callbacks for the input port (a {@link InPort}) of a {@link FlowStageLogic}. + */ +public interface InHandler { + /** + * Return the actual flow rate over the input port. + * + * @param port The input port to which the flow was pushed. + * @return The actual flow rate over the port. + */ + default float getRate(InPort port) { + return Math.min(port.getDemand(), port.getCapacity()); + } + + /** + * This method is invoked when another {@link FlowStageLogic} changes the rate of flow to the specified inlet. + * + * @param port The input port to which the flow was pushed. + * @param demand The rate of flow the output attempted to push to the port. + */ + void onPush(InPort port, float demand); + + /** + * This method is invoked when the input port is finished. + * + * @param port The input port that has finished. + * @param cause The cause of the input port being finished or <code>null</code> if the port completed successfully. + */ + void onUpstreamFinish(InPort port, Throwable cause); +} diff --git a/opendc-simulator/opendc-simulator-flow/src/main/java/org/opendc/simulator/flow2/InHandlers.java b/opendc-simulator/opendc-simulator-flow/src/main/java/org/opendc/simulator/flow2/InHandlers.java new file mode 100644 index 00000000..9d5b4bef --- /dev/null +++ b/opendc-simulator/opendc-simulator-flow/src/main/java/org/opendc/simulator/flow2/InHandlers.java @@ -0,0 +1,53 @@ +/* + * Copyright (c) 2022 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.flow2; + +/** + * A collection of common {@link InHandler} implementations. + */ +public class InHandlers { + /** + * Prevent construction of this class. + */ + private InHandlers() {} + + /** + * Return an {@link InHandler} that does nothing. + */ + public static InHandler noop() { + return NoopInHandler.INSTANCE; + } + + /** + * No-op implementation of {@link InHandler}. + */ + private static final class NoopInHandler implements InHandler { + public static final InHandler INSTANCE = new NoopInHandler(); + + @Override + public void onPush(InPort port, float demand) {} + + @Override + public void onUpstreamFinish(InPort port, Throwable cause) {} + } +} diff --git a/opendc-simulator/opendc-simulator-flow/src/main/java/org/opendc/simulator/flow2/InPort.java b/opendc-simulator/opendc-simulator-flow/src/main/java/org/opendc/simulator/flow2/InPort.java new file mode 100644 index 00000000..fba12aaf --- /dev/null +++ b/opendc-simulator/opendc-simulator-flow/src/main/java/org/opendc/simulator/flow2/InPort.java @@ -0,0 +1,214 @@ +/* + * Copyright (c) 2022 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.flow2; + +import java.time.Clock; +import java.util.Objects; + +/** + * A port that consumes a flow. + * <p> + * Input ports are represented as in-going edges in the flow graph. + */ +public final class InPort implements Inlet { + private final int id; + + private float capacity; + private float demand; + + private boolean mask; + + OutPort output; + private InHandler handler = InHandlers.noop(); + private final Clock clock; + private final String name; + private final FlowStage stage; + + InPort(FlowStage stage, String name, int id) { + this.name = name; + this.id = id; + this.stage = stage; + this.clock = stage.clock; + } + + @Override + public FlowGraph getGraph() { + return stage.parentGraph; + } + + @Override + public String getName() { + return name; + } + + /** + * Return the identifier of the {@link InPort} with respect to its stage. + */ + public int getId() { + return id; + } + + /** + * Return the current capacity of the input port. + */ + public float getCapacity() { + return capacity; + } + + /** + * Return the current demand of flow of the input port. + */ + public float getDemand() { + return demand; + } + + /** + * Return the current rate of flow of the input port. + */ + public float getRate() { + return handler.getRate(this); + } + + /** + * Pull the flow with the specified <code>capacity</code> from the input port. + * + * @param capacity The maximum throughput that the stage can receive from the input port. + */ + public void pull(float capacity) { + this.capacity = capacity; + + OutPort output = this.output; + if (output != null) { + output.pull(capacity); + } + } + + /** + * Return the current {@link InHandler} of the input port. + */ + public InHandler getHandler() { + return handler; + } + + /** + * Set the {@link InHandler} of the input port. + */ + public void setHandler(InHandler handler) { + this.handler = handler; + } + + /** + * Return the mask of this port. + * <p> + * Stages ignore events originating from masked ports. + */ + public boolean getMask() { + return mask; + } + + /** + * (Un)mask the port. + */ + public void setMask(boolean mask) { + this.mask = mask; + } + + /** + * Disconnect the input port from its (potentially) connected outlet. + * <p> + * The inlet can still be used and re-connected to another outlet. + * + * @param cause The cause for disconnecting the port or <code>null</code> when no more flow is needed. + */ + public void cancel(Throwable cause) { + demand = 0.f; + + OutPort output = this.output; + if (output != null) { + this.output = null; + output.input = null; + output.cancel(cause); + } + } + + @Override + public boolean equals(Object o) { + if (this == o) return true; + if (o == null || getClass() != o.getClass()) return false; + InPort port = (InPort) o; + return stage.equals(port.stage) && name.equals(port.name); + } + + @Override + public int hashCode() { + return Objects.hash(stage.parentGraph, name); + } + + /** + * This method is invoked when the inlet is connected to an outlet. + */ + void connect() { + OutPort output = this.output; + output.pull(capacity); + } + + /** + * Push a flow from an outlet to this inlet. + * + * @param demand The rate of flow to push. + */ + void push(float demand) { + // No-op when the rate is unchanged + if (this.demand == demand) { + return; + } + + try { + handler.onPush(this, demand); + this.demand = demand; + + if (!mask) { + stage.invalidate(clock.millis()); + } + } catch (Exception e) { + stage.doFail(e); + } + } + + /** + * This method is invoked by the connected {@link OutPort} when it finishes. + */ + void finish(Throwable cause) { + try { + long now = clock.millis(); + handler.onUpstreamFinish(this, cause); + this.demand = 0.f; + + if (!mask) { + stage.invalidate(now); + } + } catch (Exception e) { + stage.doFail(e); + } + } +} diff --git a/opendc-simulator/opendc-simulator-compute/src/main/kotlin/org/opendc/simulator/compute/power/ConstantPowerModel.kt b/opendc-simulator/opendc-simulator-flow/src/main/java/org/opendc/simulator/flow2/Inlet.java index 0fe32b0d..4a9ea6a5 100644 --- a/opendc-simulator/opendc-simulator-compute/src/main/kotlin/org/opendc/simulator/compute/power/ConstantPowerModel.kt +++ b/opendc-simulator/opendc-simulator-flow/src/main/java/org/opendc/simulator/flow2/Inlet.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2021 AtLarge Research + * Copyright (c) 2022 AtLarge Research * * Permission is hereby granted, free of charge, to any person obtaining a copy * of this software and associated documentation files (the "Software"), to deal @@ -20,13 +20,19 @@ * SOFTWARE. */ -package org.opendc.simulator.compute.power +package org.opendc.simulator.flow2; /** - * A power model which produces a constant value [power]. + * An in-going edge in a {@link FlowGraph}. */ -public class ConstantPowerModel(private val power: Double) : PowerModel { - public override fun computePower(utilization: Double): Double = power +public interface Inlet { + /** + * Return the {@link FlowGraph} to which the inlet is exposed. + */ + FlowGraph getGraph(); - override fun toString(): String = "ConstantPowerModel[power=$power]" + /** + * Return the name of the inlet. + */ + String getName(); } diff --git a/opendc-simulator/opendc-simulator-flow/src/main/java/org/opendc/simulator/flow2/InvocationStack.java b/opendc-simulator/opendc-simulator-flow/src/main/java/org/opendc/simulator/flow2/InvocationStack.java new file mode 100644 index 00000000..a5b5114b --- /dev/null +++ b/opendc-simulator/opendc-simulator-flow/src/main/java/org/opendc/simulator/flow2/InvocationStack.java @@ -0,0 +1,95 @@ +/* + * Copyright (c) 2022 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.flow2; + +import java.util.Arrays; + +/** + * A specialized monotonic stack implementation for tracking the scheduled engine invocations. + * <p> + * By using a specialized class, we reduce the overhead caused by type-erasure. + */ +final class InvocationStack { + /** + * The array of elements in the stack. + */ + private long[] elements; + + private int head = -1; + + public InvocationStack(int initialCapacity) { + elements = new long[initialCapacity]; + Arrays.fill(elements, Long.MIN_VALUE); + } + + /** + * Try to add the specified invocation to the monotonic stack. + * + * @param invocation The timestamp of the invocation. + * @return <code>true</code> if the invocation was added, <code>false</code> otherwise. + */ + boolean tryAdd(long invocation) { + final long[] es = elements; + int head = this.head; + + if (head < 0 || es[head] > invocation) { + es[head + 1] = invocation; + this.head = head + 1; + + if (head + 2 == es.length) { + doubleCapacity(); + } + + return true; + } + + return false; + } + + /** + * Remove the head invocation from the stack or return {@link Long#MAX_VALUE} if the stack is empty. + */ + long poll() { + final long[] es = elements; + int head = this.head--; + + if (head >= 0) { + return es[head]; + } + + return Long.MAX_VALUE; + } + + /** + * Doubles the capacity of this deque + */ + private void doubleCapacity() { + int oldCapacity = elements.length; + int newCapacity = oldCapacity + (oldCapacity >> 1); + if (newCapacity < 0) { + throw new IllegalStateException("Sorry, deque too big"); + } + + elements = Arrays.copyOf(elements, newCapacity); + } +} diff --git a/opendc-simulator/opendc-simulator-flow/src/main/java/org/opendc/simulator/flow2/OutHandler.java b/opendc-simulator/opendc-simulator-flow/src/main/java/org/opendc/simulator/flow2/OutHandler.java new file mode 100644 index 00000000..723c6d6b --- /dev/null +++ b/opendc-simulator/opendc-simulator-flow/src/main/java/org/opendc/simulator/flow2/OutHandler.java @@ -0,0 +1,47 @@ +/* + * Copyright (c) 2022 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.flow2; + +/** + * Collection of callbacks for the output port (a {@link OutPort}) of a {@link FlowStageLogic}. + */ +public interface OutHandler { + /** + * This method is invoked when another {@link FlowStageLogic} changes the capacity of the outlet. + * + * @param port The output port of which the capacity was changed. + * @param capacity The new capacity of the outlet. + */ + void onPull(OutPort port, float capacity); + + /** + * This method is invoked when the output port no longer accepts any flow. + * <p> + * After this callback no other callbacks will be called for this port. + * + * @param port The outlet that no longer accepts any flow. + * @param cause The cause of the output port no longer accepting any flow or <code>null</code> if the port closed + * successfully. + */ + void onDownstreamFinish(OutPort port, Throwable cause); +} diff --git a/opendc-simulator/opendc-simulator-flow/src/main/java/org/opendc/simulator/flow2/OutHandlers.java b/opendc-simulator/opendc-simulator-flow/src/main/java/org/opendc/simulator/flow2/OutHandlers.java new file mode 100644 index 00000000..8fbfda0d --- /dev/null +++ b/opendc-simulator/opendc-simulator-flow/src/main/java/org/opendc/simulator/flow2/OutHandlers.java @@ -0,0 +1,53 @@ +/* + * Copyright (c) 2022 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.flow2; + +/** + * A collection of common {@link OutHandler} implementations. + */ +public class OutHandlers { + /** + * Prevent construction of this class. + */ + private OutHandlers() {} + + /** + * Return an {@link OutHandler} that does nothing. + */ + public static OutHandler noop() { + return NoopOutHandler.INSTANCE; + } + + /** + * No-op implementation of {@link OutHandler}. + */ + private static final class NoopOutHandler implements OutHandler { + public static final OutHandler INSTANCE = new NoopOutHandler(); + + @Override + public void onPull(OutPort port, float capacity) {} + + @Override + public void onDownstreamFinish(OutPort port, Throwable cause) {} + } +} diff --git a/opendc-simulator/opendc-simulator-flow/src/main/java/org/opendc/simulator/flow2/OutPort.java b/opendc-simulator/opendc-simulator-flow/src/main/java/org/opendc/simulator/flow2/OutPort.java new file mode 100644 index 00000000..332296a0 --- /dev/null +++ b/opendc-simulator/opendc-simulator-flow/src/main/java/org/opendc/simulator/flow2/OutPort.java @@ -0,0 +1,224 @@ +/* + * Copyright (c) 2022 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.flow2; + +import java.time.Clock; +import java.util.Objects; + +/** + * A port that outputs a flow. + * <p> + * Output ports are represented as out-going edges in the flow graph. + */ +public final class OutPort implements Outlet { + private final int id; + + private float capacity; + private float demand; + + private boolean mask; + + InPort input; + private OutHandler handler = OutHandlers.noop(); + private final String name; + private final FlowStage stage; + private final Clock clock; + + OutPort(FlowStage stage, String name, int id) { + this.name = name; + this.id = id; + this.stage = stage; + this.clock = stage.clock; + } + + @Override + public FlowGraph getGraph() { + return stage.parentGraph; + } + + @Override + public String getName() { + return name; + } + + /** + * Return the identifier of the {@link OutPort} with respect to its stage. + */ + public int getId() { + return id; + } + + /** + * Return the capacity of the output port. + */ + public float getCapacity() { + return capacity; + } + + /** + * Return the current demand of flow of the output port. + */ + public float getDemand() { + return demand; + } + + /** + * Return the current rate of flow of the input port. + */ + public float getRate() { + InPort input = this.input; + if (input != null) { + return input.getRate(); + } + + return 0.f; + } + + /** + * Return the current {@link OutHandler} of the output port. + */ + public OutHandler getHandler() { + return handler; + } + + /** + * Set the {@link OutHandler} of the output port. + */ + public void setHandler(OutHandler handler) { + this.handler = handler; + } + + /** + * Return the mask of this port. + * <p> + * Stages ignore events originating from masked ports. + */ + public boolean getMask() { + return mask; + } + + /** + * (Un)mask the port. + */ + public void setMask(boolean mask) { + this.mask = mask; + } + + /** + * Push the given flow rate over output port. + * + * @param rate The rate of the flow to push. + */ + public void push(float rate) { + demand = rate; + InPort input = this.input; + + if (input != null) { + input.push(rate); + } + } + + /** + * Signal to the downstream port that the output has completed successfully and disconnect the port from its input. + * <p> + * The output port can still be used and re-connected to another input. + */ + public void complete() { + fail(null); + } + + /** + * Signal a failure to the downstream port and disconnect the port from its input. + * <p> + * The output can still be used and re-connected to another input. + */ + public void fail(Throwable cause) { + capacity = 0.f; + + InPort input = this.input; + if (input != null) { + this.input = null; + input.output = null; + input.finish(cause); + } + } + + @Override + public boolean equals(Object o) { + if (this == o) return true; + if (o == null || getClass() != o.getClass()) return false; + OutPort port = (OutPort) o; + return stage.equals(port.stage) && name.equals(port.name); + } + + @Override + public int hashCode() { + return Objects.hash(stage.parentGraph, name); + } + + /** + * This method is invoked when the outlet is connected to an inlet. + */ + void connect() { + input.push(demand); + } + + /** + * Pull from this outlet with a specified capacity. + * + * @param capacity The capacity of the inlet. + */ + void pull(float capacity) { + // No-op when outlet is not active or the rate is unchanged + if (this.capacity == capacity) { + return; + } + + try { + handler.onPull(this, capacity); + this.capacity = capacity; + + if (!mask) { + stage.invalidate(clock.millis()); + } + } catch (Exception e) { + stage.doFail(e); + } + } + + /** + * This method is invoked by the connected {@link InPort} when downstream cancels the connection. + */ + void cancel(Throwable cause) { + try { + handler.onDownstreamFinish(this, cause); + this.capacity = 0.f; + + if (!mask) { + stage.invalidate(clock.millis()); + } + } catch (Exception e) { + stage.doFail(e); + } + } +} diff --git a/opendc-simulator/opendc-simulator-flow/src/main/java/org/opendc/simulator/flow2/Outlet.java b/opendc-simulator/opendc-simulator-flow/src/main/java/org/opendc/simulator/flow2/Outlet.java new file mode 100644 index 00000000..32e19a3b --- /dev/null +++ b/opendc-simulator/opendc-simulator-flow/src/main/java/org/opendc/simulator/flow2/Outlet.java @@ -0,0 +1,38 @@ +/* + * Copyright (c) 2022 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.flow2; + +/** + * An out-going edge in a {@link FlowGraph}. + */ +public interface Outlet { + /** + * Return the {@link FlowGraph} to which the outlet is exposed. + */ + FlowGraph getGraph(); + + /** + * Return the name of the outlet. + */ + String getName(); +} diff --git a/opendc-simulator/opendc-simulator-flow/src/main/java/org/opendc/simulator/flow2/mux/FlowMultiplexer.java b/opendc-simulator/opendc-simulator-flow/src/main/java/org/opendc/simulator/flow2/mux/FlowMultiplexer.java new file mode 100644 index 00000000..dec98955 --- /dev/null +++ b/opendc-simulator/opendc-simulator-flow/src/main/java/org/opendc/simulator/flow2/mux/FlowMultiplexer.java @@ -0,0 +1,95 @@ +/* + * Copyright (c) 2022 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.flow2.mux; + +import org.opendc.simulator.flow2.FlowStageLogic; +import org.opendc.simulator.flow2.Inlet; +import org.opendc.simulator.flow2.Outlet; + +/** + * A {@link FlowStageLogic} that multiplexes multiple inputs over (possibly) multiple outputs. + */ +public interface FlowMultiplexer { + /** + * Return maximum number of inputs supported by the multiplexer. + */ + int getMaxInputs(); + + /** + * Return maximum number of outputs supported by the multiplexer. + */ + int getMaxOutputs(); + + /** + * Return the number of active inputs on this multiplexer. + */ + int getInputCount(); + + /** + * Allocate a new input on this multiplexer with the specified capacity.. + * + * @return The identifier of the input for this stage. + */ + Inlet newInput(); + + /** + * Release the input at the specified slot. + * + * @param inlet The inlet to release. + */ + void releaseInput(Inlet inlet); + + /** + * Return the number of active outputs on this multiplexer. + */ + int getOutputCount(); + + /** + * Allocate a new output on this multiplexer. + * + * @return The outlet for this stage. + */ + Outlet newOutput(); + + /** + * Release the output at the specified slot. + * + * @param outlet The outlet to release. + */ + void releaseOutput(Outlet outlet); + + /** + * Return the total input capacity of the {@link FlowMultiplexer}. + */ + float getCapacity(); + + /** + * Return the total input demand for the {@link FlowMultiplexer}. + */ + float getDemand(); + + /** + * Return the total input rate for the {@link FlowMultiplexer}. + */ + float getRate(); +} diff --git a/opendc-simulator/opendc-simulator-compute/src/main/kotlin/org/opendc/simulator/compute/SimMachineContext.kt b/opendc-simulator/opendc-simulator-flow/src/main/java/org/opendc/simulator/flow2/mux/FlowMultiplexerFactory.java index 5e3a7766..0b5b9141 100644 --- a/opendc-simulator/opendc-simulator-compute/src/main/kotlin/org/opendc/simulator/compute/SimMachineContext.kt +++ b/opendc-simulator/opendc-simulator-flow/src/main/java/org/opendc/simulator/flow2/mux/FlowMultiplexerFactory.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2020 AtLarge Research + * Copyright (c) 2022 AtLarge Research * * Permission is hereby granted, free of charge, to any person obtaining a copy * of this software and associated documentation files (the "Software"), to deal @@ -20,41 +20,32 @@ * SOFTWARE. */ -package org.opendc.simulator.compute +package org.opendc.simulator.flow2.mux; + +import org.opendc.simulator.flow2.FlowGraph; /** - * A simulated execution context in which a bootable image runs. This interface represents the - * firmware interface between the running image (e.g. operating system) and the physical or virtual firmware on - * which the image runs. + * Factory interface for a {@link FlowMultiplexer} implementation. */ -public interface SimMachineContext : AutoCloseable { - /** - * The metadata associated with the context. - */ - public val meta: Map<String, Any> - - /** - * The CPUs available on the machine. - */ - public val cpus: List<SimProcessingUnit> - - /** - * The memory interface of the machine. - */ - public val memory: SimMemory - +public interface FlowMultiplexerFactory { /** - * The network interfaces available to the workload. + * Construct a new {@link FlowMultiplexer} belonging to the specified {@link FlowGraph}. + * + * @param graph The graph to which the multiplexer belongs. */ - public val net: List<SimNetworkInterface> + FlowMultiplexer newMultiplexer(FlowGraph graph); /** - * The storage devices available to the workload. + * Return a {@link FlowMultiplexerFactory} for {@link ForwardingFlowMultiplexer} instances. */ - public val storage: List<SimStorageInterface> + static FlowMultiplexerFactory forwardingMultiplexer() { + return ForwardingFlowMultiplexer.FACTORY; + } /** - * Stop the workload. + * Return a {@link FlowMultiplexerFactory} for {@link MaxMinFlowMultiplexer} instances. */ - public override fun close() + static FlowMultiplexerFactory maxMinMultiplexer() { + return MaxMinFlowMultiplexer.FACTORY; + } } diff --git a/opendc-simulator/opendc-simulator-flow/src/main/java/org/opendc/simulator/flow2/mux/ForwardingFlowMultiplexer.java b/opendc-simulator/opendc-simulator-flow/src/main/java/org/opendc/simulator/flow2/mux/ForwardingFlowMultiplexer.java new file mode 100644 index 00000000..abe3510b --- /dev/null +++ b/opendc-simulator/opendc-simulator-flow/src/main/java/org/opendc/simulator/flow2/mux/ForwardingFlowMultiplexer.java @@ -0,0 +1,280 @@ +/* + * Copyright (c) 2022 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.flow2.mux; + +import java.util.Arrays; +import java.util.BitSet; +import org.opendc.simulator.flow2.FlowGraph; +import org.opendc.simulator.flow2.FlowStage; +import org.opendc.simulator.flow2.FlowStageLogic; +import org.opendc.simulator.flow2.InHandler; +import org.opendc.simulator.flow2.InPort; +import org.opendc.simulator.flow2.Inlet; +import org.opendc.simulator.flow2.OutHandler; +import org.opendc.simulator.flow2.OutPort; +import org.opendc.simulator.flow2.Outlet; + +/** + * A {@link FlowMultiplexer} implementation that allocates inputs to the outputs of the multiplexer exclusively. + * This means that a single input is directly connected to an output and that the multiplexer can only support as many + * inputs as outputs. + */ +public final class ForwardingFlowMultiplexer implements FlowMultiplexer, FlowStageLogic { + /** + * Factory implementation for this implementation. + */ + static FlowMultiplexerFactory FACTORY = ForwardingFlowMultiplexer::new; + + public final IdleInHandler IDLE_IN_HANDLER = new IdleInHandler(); + public final IdleOutHandler IDLE_OUT_HANDLER = new IdleOutHandler(); + + private final FlowStage stage; + + private InPort[] inlets; + private OutPort[] outlets; + private final BitSet activeInputs; + private final BitSet activeOutputs; + private final BitSet availableOutputs; + + private float capacity = 0.f; + private float demand = 0.f; + + public ForwardingFlowMultiplexer(FlowGraph graph) { + this.stage = graph.newStage(this); + + this.inlets = new InPort[4]; + this.activeInputs = new BitSet(); + this.outlets = new OutPort[4]; + this.activeOutputs = new BitSet(); + this.availableOutputs = new BitSet(); + } + + @Override + public float getCapacity() { + return capacity; + } + + @Override + public float getDemand() { + return demand; + } + + @Override + public float getRate() { + final BitSet activeOutputs = this.activeOutputs; + final OutPort[] outlets = this.outlets; + float rate = 0.f; + for (int i = activeOutputs.nextSetBit(0); i != -1; i = activeOutputs.nextSetBit(i + 1)) { + rate += outlets[i].getRate(); + } + return rate; + } + + @Override + public int getMaxInputs() { + return getOutputCount(); + } + + @Override + public int getMaxOutputs() { + return Integer.MAX_VALUE; + } + + @Override + public int getInputCount() { + return activeInputs.length(); + } + + @Override + public Inlet newInput() { + final BitSet activeInputs = this.activeInputs; + int slot = activeInputs.nextClearBit(0); + + InPort inPort = stage.getInlet("in" + slot); + inPort.setMask(true); + + InPort[] inlets = this.inlets; + if (slot >= inlets.length) { + int newLength = inlets.length + (inlets.length >> 1); + inlets = Arrays.copyOf(inlets, newLength); + this.inlets = inlets; + } + + final BitSet availableOutputs = this.availableOutputs; + int outSlot = availableOutputs.nextSetBit(0); + + if (outSlot < 0) { + throw new IllegalStateException("No capacity available for a new input"); + } + + inlets[slot] = inPort; + activeInputs.set(slot); + + OutPort outPort = outlets[outSlot]; + availableOutputs.clear(outSlot); + + inPort.setHandler(new ForwardingInHandler(outPort)); + outPort.setHandler(new ForwardingOutHandler(inPort)); + + inPort.pull(outPort.getCapacity()); + + return inPort; + } + + @Override + public void releaseInput(Inlet inlet) { + InPort port = (InPort) inlet; + int slot = port.getId(); + + final BitSet activeInputs = this.activeInputs; + + if (!activeInputs.get(slot)) { + return; + } + + port.cancel(null); + activeInputs.clear(slot); + + ForwardingInHandler inHandler = (ForwardingInHandler) port.getHandler(); + availableOutputs.set(inHandler.output.getId()); + + port.setHandler(IDLE_IN_HANDLER); + } + + @Override + public int getOutputCount() { + return activeOutputs.length(); + } + + @Override + public Outlet newOutput() { + final BitSet activeOutputs = this.activeOutputs; + int slot = activeOutputs.nextClearBit(0); + + OutPort port = stage.getOutlet("out" + slot); + OutPort[] outlets = this.outlets; + if (slot >= outlets.length) { + int newLength = outlets.length + (outlets.length >> 1); + outlets = Arrays.copyOf(outlets, newLength); + this.outlets = outlets; + } + outlets[slot] = port; + + activeOutputs.set(slot); + availableOutputs.set(slot); + return port; + } + + @Override + public void releaseOutput(Outlet outlet) { + OutPort port = (OutPort) outlet; + int slot = port.getId(); + activeInputs.clear(slot); + availableOutputs.clear(slot); + port.complete(); + + port.setHandler(IDLE_OUT_HANDLER); + } + + @Override + public long onUpdate(FlowStage ctx, long now) { + return Long.MAX_VALUE; + } + + class ForwardingInHandler implements InHandler { + final OutPort output; + + ForwardingInHandler(OutPort output) { + this.output = output; + } + + @Override + public float getRate(InPort port) { + return output.getRate(); + } + + @Override + public void onPush(InPort port, float rate) { + ForwardingFlowMultiplexer.this.demand += -port.getDemand() + rate; + + output.push(rate); + } + + @Override + public void onUpstreamFinish(InPort port, Throwable cause) { + ForwardingFlowMultiplexer.this.demand -= port.getDemand(); + + final OutPort output = this.output; + output.push(0.f); + + releaseInput(port); + } + } + + private class ForwardingOutHandler implements OutHandler { + private final InPort input; + + ForwardingOutHandler(InPort input) { + this.input = input; + } + + @Override + public void onPull(OutPort port, float capacity) { + ForwardingFlowMultiplexer.this.capacity += -port.getCapacity() + capacity; + + input.pull(capacity); + } + + @Override + public void onDownstreamFinish(OutPort port, Throwable cause) { + ForwardingFlowMultiplexer.this.capacity -= port.getCapacity(); + + input.cancel(cause); + + releaseOutput(port); + } + } + + private static class IdleInHandler implements InHandler { + @Override + public float getRate(InPort port) { + return 0.f; + } + + @Override + public void onPush(InPort port, float rate) { + port.cancel(new IllegalStateException("Inlet is not allocated")); + } + + @Override + public void onUpstreamFinish(InPort port, Throwable cause) {} + } + + private static class IdleOutHandler implements OutHandler { + @Override + public void onPull(OutPort port, float capacity) {} + + @Override + public void onDownstreamFinish(OutPort port, Throwable cause) {} + } +} diff --git a/opendc-simulator/opendc-simulator-flow/src/main/java/org/opendc/simulator/flow2/mux/MaxMinFlowMultiplexer.java b/opendc-simulator/opendc-simulator-flow/src/main/java/org/opendc/simulator/flow2/mux/MaxMinFlowMultiplexer.java new file mode 100644 index 00000000..ac5c4f5c --- /dev/null +++ b/opendc-simulator/opendc-simulator-flow/src/main/java/org/opendc/simulator/flow2/mux/MaxMinFlowMultiplexer.java @@ -0,0 +1,297 @@ +/* + * Copyright (c) 2022 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.flow2.mux; + +import java.util.Arrays; +import java.util.BitSet; +import org.opendc.simulator.flow2.FlowGraph; +import org.opendc.simulator.flow2.FlowStage; +import org.opendc.simulator.flow2.FlowStageLogic; +import org.opendc.simulator.flow2.InHandler; +import org.opendc.simulator.flow2.InPort; +import org.opendc.simulator.flow2.Inlet; +import org.opendc.simulator.flow2.OutHandler; +import org.opendc.simulator.flow2.OutPort; +import org.opendc.simulator.flow2.Outlet; + +/** + * A {@link FlowMultiplexer} implementation that distributes the available capacity of the outputs over the inputs + * using max-min fair sharing. + * <p> + * The max-min fair sharing algorithm of this multiplexer ensures that each input receives a fair share of the combined + * output capacity, but allows individual inputs to use more capacity if there is still capacity left. + */ +public final class MaxMinFlowMultiplexer implements FlowMultiplexer, FlowStageLogic { + /** + * Factory implementation for this implementation. + */ + static FlowMultiplexerFactory FACTORY = MaxMinFlowMultiplexer::new; + + private final FlowStage stage; + private final BitSet activeInputs; + private final BitSet activeOutputs; + + private float capacity = 0.f; + private float demand = 0.f; + private float rate = 0.f; + + private InPort[] inlets; + private long[] inputs; + private float[] rates; + private OutPort[] outlets; + + private final MultiplexerInHandler inHandler = new MultiplexerInHandler(); + private final MultiplexerOutHandler outHandler = new MultiplexerOutHandler(); + + /** + * Construct a {@link MaxMinFlowMultiplexer} instance. + * + * @param graph The {@link FlowGraph} to add the multiplexer to. + */ + public MaxMinFlowMultiplexer(FlowGraph graph) { + this.stage = graph.newStage(this); + this.activeInputs = new BitSet(); + this.activeOutputs = new BitSet(); + + this.inlets = new InPort[4]; + this.inputs = new long[4]; + this.rates = new float[4]; + this.outlets = new OutPort[4]; + } + + @Override + public float getCapacity() { + return capacity; + } + + @Override + public float getDemand() { + return demand; + } + + @Override + public float getRate() { + return rate; + } + + @Override + public int getMaxInputs() { + return Integer.MAX_VALUE; + } + + @Override + public int getMaxOutputs() { + return Integer.MAX_VALUE; + } + + @Override + public long onUpdate(FlowStage ctx, long now) { + float capacity = this.capacity; + float demand = this.demand; + float rate = demand; + + if (demand > capacity) { + rate = redistributeCapacity(inlets, inputs, rates, capacity); + } + + if (this.rate != rate) { + // Only update the outputs if the output rate has changed + this.rate = rate; + + changeRate(activeOutputs, outlets, capacity, rate); + } + + return Long.MAX_VALUE; + } + + @Override + public int getInputCount() { + return activeInputs.length(); + } + + @Override + public Inlet newInput() { + final BitSet activeInputs = this.activeInputs; + int slot = activeInputs.nextClearBit(0); + + InPort port = stage.getInlet("in" + slot); + port.setHandler(inHandler); + port.pull(this.capacity); + + InPort[] inlets = this.inlets; + if (slot >= inlets.length) { + int newLength = inlets.length + (inlets.length >> 1); + inlets = Arrays.copyOf(inlets, newLength); + inputs = Arrays.copyOf(inputs, newLength); + rates = Arrays.copyOf(rates, newLength); + this.inlets = inlets; + } + inlets[slot] = port; + + activeInputs.set(slot); + return port; + } + + @Override + public void releaseInput(Inlet inlet) { + InPort port = (InPort) inlet; + + activeInputs.clear(port.getId()); + port.cancel(null); + } + + @Override + public int getOutputCount() { + return activeOutputs.length(); + } + + @Override + public Outlet newOutput() { + final BitSet activeOutputs = this.activeOutputs; + int slot = activeOutputs.nextClearBit(0); + + OutPort port = stage.getOutlet("out" + slot); + port.setHandler(outHandler); + + OutPort[] outlets = this.outlets; + if (slot >= outlets.length) { + int newLength = outlets.length + (outlets.length >> 1); + outlets = Arrays.copyOf(outlets, newLength); + this.outlets = outlets; + } + outlets[slot] = port; + + activeOutputs.set(slot); + return port; + } + + @Override + public void releaseOutput(Outlet outlet) { + OutPort port = (OutPort) outlet; + activeInputs.clear(port.getId()); + port.complete(); + } + + /** + * Helper function to redistribute the specified capacity across the inlets. + */ + private static float redistributeCapacity(InPort[] inlets, long[] inputs, float[] rates, float capacity) { + // If the demand is higher than the capacity, we need use max-min fair sharing to distribute the + // constrained capacity across the inputs. + for (int i = 0; i < inputs.length; i++) { + InPort inlet = inlets[i]; + if (inlet == null) { + break; + } + + inputs[i] = ((long) Float.floatToRawIntBits(inlet.getDemand()) << 32) | (i & 0xFFFFFFFFL); + } + Arrays.sort(inputs); + + float availableCapacity = capacity; + int inputSize = inputs.length; + + // Divide the available output capacity fairly over the inputs using max-min fair sharing + for (int i = 0; i < inputs.length; i++) { + long v = inputs[i]; + int slot = (int) v; + float d = Float.intBitsToFloat((int) (v >> 32)); + + if (d == 0.0) { + continue; + } + + float availableShare = availableCapacity / (inputSize - i); + float r = Math.min(d, availableShare); + + rates[slot] = r; + availableCapacity -= r; + } + + return capacity - availableCapacity; + } + + /** + * Helper method to change the rate of the outlets. + */ + private static void changeRate(BitSet activeOutputs, OutPort[] outlets, float capacity, float rate) { + // Divide the requests over the available capacity of the input resources fairly + for (int i = activeOutputs.nextSetBit(0); i != -1; i = activeOutputs.nextSetBit(i + 1)) { + OutPort outlet = outlets[i]; + float fraction = outlet.getCapacity() / capacity; + outlet.push(rate * fraction); + } + } + + /** + * A {@link InHandler} implementation for the multiplexer inputs. + */ + private class MultiplexerInHandler implements InHandler { + @Override + public float getRate(InPort port) { + return rates[port.getId()]; + } + + @Override + public void onPush(InPort port, float demand) { + MaxMinFlowMultiplexer.this.demand += -port.getDemand() + demand; + rates[port.getId()] = demand; + } + + @Override + public void onUpstreamFinish(InPort port, Throwable cause) { + MaxMinFlowMultiplexer.this.demand -= port.getDemand(); + releaseInput(port); + rates[port.getId()] = 0.f; + } + } + + /** + * A {@link OutHandler} implementation for the multiplexer outputs. + */ + private class MultiplexerOutHandler implements OutHandler { + @Override + public void onPull(OutPort port, float capacity) { + float newCapacity = MaxMinFlowMultiplexer.this.capacity - port.getCapacity() + capacity; + MaxMinFlowMultiplexer.this.capacity = newCapacity; + changeInletCapacity(newCapacity); + } + + @Override + public void onDownstreamFinish(OutPort port, Throwable cause) { + float newCapacity = MaxMinFlowMultiplexer.this.capacity - port.getCapacity(); + MaxMinFlowMultiplexer.this.capacity = newCapacity; + releaseOutput(port); + changeInletCapacity(newCapacity); + } + + private void changeInletCapacity(float capacity) { + BitSet activeInputs = MaxMinFlowMultiplexer.this.activeInputs; + InPort[] inlets = MaxMinFlowMultiplexer.this.inlets; + + for (int i = activeInputs.nextSetBit(0); i != -1; i = activeInputs.nextSetBit(i + 1)) { + inlets[i].pull(capacity); + } + } + } +} diff --git a/opendc-simulator/opendc-simulator-flow/src/main/kotlin/org/opendc/simulator/flow/internal/Constants.kt b/opendc-simulator/opendc-simulator-flow/src/main/java/org/opendc/simulator/flow2/sink/FlowSink.java index 450195ec..69c94708 100644 --- a/opendc-simulator/opendc-simulator-flow/src/main/kotlin/org/opendc/simulator/flow/internal/Constants.kt +++ b/opendc-simulator/opendc-simulator-flow/src/main/java/org/opendc/simulator/flow2/sink/FlowSink.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2021 AtLarge Research + * Copyright (c) 2022 AtLarge Research * * Permission is hereby granted, free of charge, to any person obtaining a copy * of this software and associated documentation files (the "Software"), to deal @@ -20,9 +20,17 @@ * SOFTWARE. */ -package org.opendc.simulator.flow.internal +package org.opendc.simulator.flow2.sink; + +import org.opendc.simulator.flow2.FlowStage; +import org.opendc.simulator.flow2.Inlet; /** - * Constant for converting milliseconds into seconds. + * A {@link FlowStage} with a single input. */ -internal const val D_MS_TO_S = 1 / 1000.0 +public interface FlowSink { + /** + * Return the input of this {@link FlowSink}. + */ + Inlet getInput(); +} diff --git a/opendc-simulator/opendc-simulator-flow/src/main/java/org/opendc/simulator/flow2/sink/SimpleFlowSink.java b/opendc-simulator/opendc-simulator-flow/src/main/java/org/opendc/simulator/flow2/sink/SimpleFlowSink.java new file mode 100644 index 00000000..fdfe5ee8 --- /dev/null +++ b/opendc-simulator/opendc-simulator-flow/src/main/java/org/opendc/simulator/flow2/sink/SimpleFlowSink.java @@ -0,0 +1,123 @@ +/* + * Copyright (c) 2022 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.flow2.sink; + +import org.opendc.simulator.flow2.FlowGraph; +import org.opendc.simulator.flow2.FlowStage; +import org.opendc.simulator.flow2.FlowStageLogic; +import org.opendc.simulator.flow2.InHandler; +import org.opendc.simulator.flow2.InPort; +import org.opendc.simulator.flow2.Inlet; + +/** + * A sink with a fixed capacity. + */ +public final class SimpleFlowSink implements FlowSink, FlowStageLogic { + private final FlowStage stage; + private final InPort input; + private final Handler handler; + + /** + * Construct a new {@link SimpleFlowSink} with the specified initial capacity. + * + * @param graph The graph to add the sink to. + * @param initialCapacity The initial capacity of the sink. + */ + public SimpleFlowSink(FlowGraph graph, float initialCapacity) { + this.stage = graph.newStage(this); + this.handler = new Handler(); + this.input = stage.getInlet("in"); + this.input.pull(initialCapacity); + this.input.setMask(true); + this.input.setHandler(handler); + } + + /** + * Return the {@link Inlet} of this sink. + */ + @Override + public Inlet getInput() { + return input; + } + + /** + * Return the capacity of the sink. + */ + public float getCapacity() { + return input.getCapacity(); + } + + /** + * Update the capacity of the sink. + * + * @param capacity The new capacity to update the sink to. + */ + public void setCapacity(float capacity) { + input.pull(capacity); + stage.invalidate(); + } + + /** + * Return the flow rate of the sink. + */ + public float getRate() { + return input.getRate(); + } + + /** + * Remove this node from the graph. + */ + public void close() { + stage.close(); + } + + @Override + public long onUpdate(FlowStage ctx, long now) { + InPort input = this.input; + handler.rate = Math.min(input.getDemand(), input.getCapacity()); + return Long.MAX_VALUE; + } + + /** + * The {@link InHandler} implementation for the sink. + */ + private static final class Handler implements InHandler { + float rate; + + @Override + public float getRate(InPort port) { + return rate; + } + + @Override + public void onPush(InPort port, float demand) { + float capacity = port.getCapacity(); + rate = Math.min(demand, capacity); + } + + @Override + public void onUpstreamFinish(InPort port, Throwable cause) { + rate = 0.f; + } + } +} diff --git a/opendc-simulator/opendc-simulator-flow/src/main/kotlin/org/opendc/simulator/flow/FlowConsumerContext.kt b/opendc-simulator/opendc-simulator-flow/src/main/java/org/opendc/simulator/flow2/source/EmptyFlowSource.java index 98922ab3..2dcc66e4 100644 --- a/opendc-simulator/opendc-simulator-flow/src/main/kotlin/org/opendc/simulator/flow/FlowConsumerContext.kt +++ b/opendc-simulator/opendc-simulator-flow/src/main/java/org/opendc/simulator/flow2/source/EmptyFlowSource.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2021 AtLarge Research + * Copyright (c) 2022 AtLarge Research * * Permission is hereby granted, free of charge, to any person obtaining a copy * of this software and associated documentation files (the "Software"), to deal @@ -20,43 +20,46 @@ * SOFTWARE. */ -package org.opendc.simulator.flow +package org.opendc.simulator.flow2.source; + +import org.opendc.simulator.flow2.FlowGraph; +import org.opendc.simulator.flow2.FlowStage; +import org.opendc.simulator.flow2.FlowStageLogic; +import org.opendc.simulator.flow2.OutPort; +import org.opendc.simulator.flow2.Outlet; /** - * A controllable [FlowConnection]. - * - * This interface is used by [FlowConsumer]s to control the connection between it and the source. + * An empty {@link FlowSource}. */ -public interface FlowConsumerContext : FlowConnection { - /** - * The deadline of the source. - */ - public val deadline: Long +public final class EmptyFlowSource implements FlowSource, FlowStageLogic { + private final FlowStage stage; + private final OutPort output; /** - * The capacity of the connection. + * Construct a new {@link EmptyFlowSource}. */ - public override var capacity: Double + public EmptyFlowSource(FlowGraph graph) { + this.stage = graph.newStage(this); + this.output = stage.getOutlet("out"); + } /** - * A flag to control whether [FlowConsumerLogic.onConverge] should be invoked for the consumer. + * Return the {@link Outlet} of the source. */ - public var shouldConsumerConverge: Boolean + @Override + public Outlet getOutput() { + return output; + } /** - * A flag to control whether the timers for the [FlowSource] should be enabled. + * Remove this node from the graph. */ - public var enableTimers: Boolean + public void close() { + stage.close(); + } - /** - * Start the flow over the connection. - */ - public fun start() - - /** - * Synchronously pull the source of the connection. - * - * @param now The timestamp at which the connection is pulled. - */ - public fun pullSync(now: Long) + @Override + public long onUpdate(FlowStage ctx, long now) { + return Long.MAX_VALUE; + } } diff --git a/opendc-simulator/opendc-simulator-flow/src/main/java/org/opendc/simulator/flow2/source/FlowSource.java b/opendc-simulator/opendc-simulator-flow/src/main/java/org/opendc/simulator/flow2/source/FlowSource.java new file mode 100644 index 00000000..f9432c33 --- /dev/null +++ b/opendc-simulator/opendc-simulator-flow/src/main/java/org/opendc/simulator/flow2/source/FlowSource.java @@ -0,0 +1,36 @@ +/* + * Copyright (c) 2022 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.flow2.source; + +import org.opendc.simulator.flow2.FlowStage; +import org.opendc.simulator.flow2.Outlet; + +/** + * A {@link FlowStage} with a single output. + */ +public interface FlowSource { + /** + * Return the output of this {@link FlowSource}. + */ + Outlet getOutput(); +} diff --git a/opendc-simulator/opendc-simulator-flow/src/main/java/org/opendc/simulator/flow2/source/RuntimeFlowSource.java b/opendc-simulator/opendc-simulator-flow/src/main/java/org/opendc/simulator/flow2/source/RuntimeFlowSource.java new file mode 100644 index 00000000..c09987cd --- /dev/null +++ b/opendc-simulator/opendc-simulator-flow/src/main/java/org/opendc/simulator/flow2/source/RuntimeFlowSource.java @@ -0,0 +1,128 @@ +/* + * Copyright (c) 2022 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.flow2.source; + +import java.util.function.Consumer; +import org.opendc.simulator.flow2.FlowGraph; +import org.opendc.simulator.flow2.FlowStage; +import org.opendc.simulator.flow2.FlowStageLogic; +import org.opendc.simulator.flow2.OutHandler; +import org.opendc.simulator.flow2.OutPort; +import org.opendc.simulator.flow2.Outlet; + +/** + * A {@link FlowSource} that ensures a flow is emitted for a specified amount of time at some utilization. + */ +public class RuntimeFlowSource implements FlowSource, FlowStageLogic { + private final float utilization; + + private final FlowStage stage; + private final OutPort output; + private final Consumer<RuntimeFlowSource> completionHandler; + + private long duration; + private long lastPull; + + /** + * Construct a {@link RuntimeFlowSource} instance. + * + * @param graph The {@link FlowGraph} to which this source belongs. + * @param duration The duration of the source. + * @param utilization The utilization of the capacity of the outlet. + * @param completionHandler A callback invoked when the source completes. + */ + public RuntimeFlowSource( + FlowGraph graph, long duration, float utilization, Consumer<RuntimeFlowSource> completionHandler) { + if (duration <= 0) { + throw new IllegalArgumentException("Duration must be positive and non-zero"); + } + + if (utilization <= 0.0) { + throw new IllegalArgumentException("Utilization must be positive and non-zero"); + } + + this.stage = graph.newStage(this); + this.output = stage.getOutlet("out"); + this.output.setHandler(new OutHandler() { + @Override + public void onPull(OutPort port, float capacity) {} + + @Override + public void onDownstreamFinish(OutPort port, Throwable cause) { + // Source cannot complete without re-connecting to another sink, so mark the source as completed + completionHandler.accept(RuntimeFlowSource.this); + } + }); + this.duration = duration; + this.utilization = utilization; + this.completionHandler = completionHandler; + this.lastPull = graph.getEngine().getClock().millis(); + } + + /** + * Construct a new {@link RuntimeFlowSource}. + * + * @param graph The {@link FlowGraph} to which this source belongs. + * @param duration The duration of the source. + * @param utilization The utilization of the capacity of the outlet. + */ + public RuntimeFlowSource(FlowGraph graph, long duration, float utilization) { + this(graph, duration, utilization, RuntimeFlowSource::close); + } + + /** + * Return the {@link Outlet} of the source. + */ + @Override + public Outlet getOutput() { + return output; + } + + /** + * Remove this node from the graph. + */ + public void close() { + stage.close(); + } + + @Override + public long onUpdate(FlowStage ctx, long now) { + long lastPull = this.lastPull; + this.lastPull = now; + + long delta = Math.max(0, now - lastPull); + + OutPort output = this.output; + float limit = output.getCapacity() * utilization; + long duration = this.duration - delta; + + if (duration <= 0) { + completionHandler.accept(this); + return Long.MAX_VALUE; + } + + this.duration = duration; + output.push(limit); + return now + duration; + } +} diff --git a/opendc-simulator/opendc-simulator-flow/src/main/java/org/opendc/simulator/flow2/source/SimpleFlowSource.java b/opendc-simulator/opendc-simulator-flow/src/main/java/org/opendc/simulator/flow2/source/SimpleFlowSource.java new file mode 100644 index 00000000..a0e9cb9d --- /dev/null +++ b/opendc-simulator/opendc-simulator-flow/src/main/java/org/opendc/simulator/flow2/source/SimpleFlowSource.java @@ -0,0 +1,131 @@ +/* + * Copyright (c) 2022 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.flow2.source; + +import java.util.function.Consumer; +import org.opendc.simulator.flow2.FlowGraph; +import org.opendc.simulator.flow2.FlowStage; +import org.opendc.simulator.flow2.FlowStageLogic; +import org.opendc.simulator.flow2.OutHandler; +import org.opendc.simulator.flow2.OutPort; +import org.opendc.simulator.flow2.Outlet; + +/** + * A flow source that contains a fixed amount and is pushed with a given utilization. + */ +public final class SimpleFlowSource implements FlowSource, FlowStageLogic { + private final float utilization; + private float remainingAmount; + private long lastPull; + + private final FlowStage stage; + private final OutPort output; + private final Consumer<SimpleFlowSource> completionHandler; + + /** + * Construct a new {@link SimpleFlowSource}. + * + * @param graph The {@link FlowGraph} to which this source belongs. + * @param amount The amount to transfer via the outlet. + * @param utilization The utilization of the capacity of the outlet. + * @param completionHandler A callback invoked when the source completes. + */ + public SimpleFlowSource( + FlowGraph graph, float amount, float utilization, Consumer<SimpleFlowSource> completionHandler) { + if (amount < 0.0) { + throw new IllegalArgumentException("Amount must be non-negative"); + } + + if (utilization <= 0.0) { + throw new IllegalArgumentException("Utilization must be positive and non-zero"); + } + + this.stage = graph.newStage(this); + this.output = stage.getOutlet("out"); + this.output.setHandler(new OutHandler() { + @Override + public void onPull(OutPort port, float capacity) {} + + @Override + public void onDownstreamFinish(OutPort port, Throwable cause) { + // Source cannot complete without re-connecting to another sink, so mark the source as completed + completionHandler.accept(SimpleFlowSource.this); + } + }); + this.completionHandler = completionHandler; + this.utilization = utilization; + this.remainingAmount = amount; + this.lastPull = graph.getEngine().getClock().millis(); + } + + /** + * Construct a new {@link SimpleFlowSource}. + * + * @param graph The {@link FlowGraph} to which this source belongs. + * @param amount The amount to transfer via the outlet. + * @param utilization The utilization of the capacity of the outlet. + */ + public SimpleFlowSource(FlowGraph graph, float amount, float utilization) { + this(graph, amount, utilization, SimpleFlowSource::close); + } + + /** + * Return the {@link Outlet} of the source. + */ + @Override + public Outlet getOutput() { + return output; + } + + /** + * Remove this node from the graph. + */ + public void close() { + stage.close(); + } + + @Override + public long onUpdate(FlowStage ctx, long now) { + long lastPull = this.lastPull; + this.lastPull = now; + + long delta = Math.max(0, now - lastPull); + + OutPort output = this.output; + float consumed = output.getRate() * delta / 1000.f; + float limit = output.getCapacity() * utilization; + + float remainingAmount = this.remainingAmount - consumed; + this.remainingAmount = remainingAmount; + + long duration = (long) Math.ceil(remainingAmount / limit * 1000); + + if (duration <= 0) { + completionHandler.accept(this); + return Long.MAX_VALUE; + } + + output.push(limit); + return now + duration; + } +} diff --git a/opendc-simulator/opendc-simulator-flow/src/main/java/org/opendc/simulator/flow2/source/TraceFlowSource.java b/opendc-simulator/opendc-simulator-flow/src/main/java/org/opendc/simulator/flow2/source/TraceFlowSource.java new file mode 100644 index 00000000..e8abc2d7 --- /dev/null +++ b/opendc-simulator/opendc-simulator-flow/src/main/java/org/opendc/simulator/flow2/source/TraceFlowSource.java @@ -0,0 +1,151 @@ +/* + * Copyright (c) 2022 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.flow2.source; + +import java.util.function.Consumer; +import org.opendc.simulator.flow2.FlowGraph; +import org.opendc.simulator.flow2.FlowStage; +import org.opendc.simulator.flow2.FlowStageLogic; +import org.opendc.simulator.flow2.OutHandler; +import org.opendc.simulator.flow2.OutPort; +import org.opendc.simulator.flow2.Outlet; + +/** + * A flow source that replays a sequence of fragments, each indicating the flow rate for some period of time. + */ +public final class TraceFlowSource implements FlowSource, FlowStageLogic { + private final OutPort output; + private final long[] deadlines; + private final float[] usages; + private final int size; + private int index; + + private final FlowStage stage; + private final Consumer<TraceFlowSource> completionHandler; + + /** + * Construct a {@link TraceFlowSource}. + * + * @param graph The {@link FlowGraph} to which the source belongs. + * @param trace The {@link Trace} to replay. + * @param completionHandler The completion handler to invoke when the source finishes. + */ + public TraceFlowSource(FlowGraph graph, Trace trace, Consumer<TraceFlowSource> completionHandler) { + this.stage = graph.newStage(this); + this.output = stage.getOutlet("out"); + this.output.setHandler(new OutHandler() { + @Override + public void onPull(OutPort port, float capacity) {} + + @Override + public void onDownstreamFinish(OutPort port, Throwable cause) { + // Source cannot complete without re-connecting to another sink, so mark the source as completed + completionHandler.accept(TraceFlowSource.this); + } + }); + this.deadlines = trace.deadlines; + this.usages = trace.usages; + this.size = trace.size; + this.completionHandler = completionHandler; + } + + /** + * Construct a {@link TraceFlowSource}. + * + * @param graph The {@link FlowGraph} to which the source belongs. + * @param trace The {@link Trace} to replay. + */ + public TraceFlowSource(FlowGraph graph, Trace trace) { + this(graph, trace, TraceFlowSource::close); + } + + @Override + public Outlet getOutput() { + return output; + } + + /** + * Remove this node from the graph. + */ + public void close() { + stage.close(); + } + + @Override + public long onUpdate(FlowStage ctx, long now) { + int size = this.size; + int index = this.index; + long[] deadlines = this.deadlines; + long deadline; + + do { + deadline = deadlines[index]; + } while (deadline <= now && ++index < size); + + if (index >= size) { + output.push(0.0f); + completionHandler.accept(this); + return Long.MAX_VALUE; + } + + this.index = index; + float usage = usages[index]; + output.push(usage); + + return deadline; + } + + /** + * A trace describes the workload over time. + */ + public static final class Trace { + private final long[] deadlines; + private final float[] usages; + private final int size; + + /** + * Construct a {@link Trace}. + * + * @param deadlines The deadlines of the trace fragments. + * @param usages The usages of the trace fragments. + * @param size The size of the trace. + */ + public Trace(long[] deadlines, float[] usages, int size) { + this.deadlines = deadlines; + this.usages = usages; + this.size = size; + } + + public long[] getDeadlines() { + return deadlines; + } + + public float[] getUsages() { + return usages; + } + + public int getSize() { + return size; + } + } +} diff --git a/opendc-simulator/opendc-simulator-compute/src/main/kotlin/org/opendc/simulator/compute/SimProcessingUnit.kt b/opendc-simulator/opendc-simulator-flow/src/main/java/org/opendc/simulator/flow2/util/FlowTransform.java index c9f36ece..51ea7df3 100644 --- a/opendc-simulator/opendc-simulator-compute/src/main/kotlin/org/opendc/simulator/compute/SimProcessingUnit.kt +++ b/opendc-simulator/opendc-simulator-flow/src/main/java/org/opendc/simulator/flow2/util/FlowTransform.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2021 AtLarge Research + * Copyright (c) 2022 AtLarge Research * * Permission is hereby granted, free of charge, to any person obtaining a copy * of this software and associated documentation files (the "Software"), to deal @@ -20,22 +20,22 @@ * SOFTWARE. */ -package org.opendc.simulator.compute +package org.opendc.simulator.flow2.util; -import org.opendc.simulator.compute.model.ProcessingUnit -import org.opendc.simulator.flow.FlowConsumer +import org.opendc.simulator.flow2.FlowGraph; /** - * A simulated processing unit. + * A {@link FlowTransform} describes a transformation between two components in a {@link FlowGraph} that might operate + * at different units of flow. */ -public interface SimProcessingUnit : FlowConsumer { +public interface FlowTransform { /** - * The capacity of the processing unit, which can be adjusted by the workload if supported by the machine. + * Apply the transform to the specified flow rate. */ - public override var capacity: Double + float apply(float value); /** - * The model representing the static properties of the processing unit. + * Apply the inverse of the transformation to the specified flow rate. */ - public val model: ProcessingUnit + float applyInverse(float value); } diff --git a/opendc-simulator/opendc-simulator-flow/src/main/java/org/opendc/simulator/flow2/util/FlowTransformer.java b/opendc-simulator/opendc-simulator-flow/src/main/java/org/opendc/simulator/flow2/util/FlowTransformer.java new file mode 100644 index 00000000..852240d8 --- /dev/null +++ b/opendc-simulator/opendc-simulator-flow/src/main/java/org/opendc/simulator/flow2/util/FlowTransformer.java @@ -0,0 +1,124 @@ +/* + * Copyright (c) 2022 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.flow2.util; + +import org.opendc.simulator.flow2.*; +import org.opendc.simulator.flow2.sink.FlowSink; +import org.opendc.simulator.flow2.source.FlowSource; + +/** + * Helper class to transform flow from outlet to inlet. + */ +public final class FlowTransformer implements FlowStageLogic, FlowSource, FlowSink { + private final FlowStage stage; + private final InPort input; + private final OutPort output; + + /** + * Construct a new {@link FlowTransformer}. + */ + public FlowTransformer(FlowGraph graph, FlowTransform transform) { + this.stage = graph.newStage(this); + this.input = stage.getInlet("in"); + this.output = stage.getOutlet("out"); + + this.input.setHandler(new ForwardInHandler(output, transform)); + this.input.setMask(true); + this.output.setHandler(new ForwardOutHandler(input, transform)); + this.output.setMask(true); + } + + /** + * Return the {@link Outlet} of the transformer. + */ + @Override + public Outlet getOutput() { + return output; + } + + /** + * Return the {@link Inlet} of the transformer. + */ + @Override + public Inlet getInput() { + return input; + } + + /** + * Close the transformer. + */ + void close() { + stage.close(); + } + + @Override + public long onUpdate(FlowStage ctx, long now) { + return Long.MAX_VALUE; + } + + private static class ForwardInHandler implements InHandler { + private final OutPort output; + private final FlowTransform transform; + + ForwardInHandler(OutPort output, FlowTransform transform) { + this.output = output; + this.transform = transform; + } + + @Override + public float getRate(InPort port) { + return transform.applyInverse(output.getRate()); + } + + @Override + public void onPush(InPort port, float demand) { + float rate = transform.apply(demand); + output.push(rate); + } + + @Override + public void onUpstreamFinish(InPort port, Throwable cause) { + output.fail(cause); + } + } + + private static class ForwardOutHandler implements OutHandler { + private final InPort input; + private final FlowTransform transform; + + ForwardOutHandler(InPort input, FlowTransform transform) { + this.input = input; + this.transform = transform; + } + + @Override + public void onPull(OutPort port, float capacity) { + input.pull(transform.applyInverse(capacity)); + } + + @Override + public void onDownstreamFinish(OutPort port, Throwable cause) { + input.cancel(cause); + } + } +} diff --git a/opendc-simulator/opendc-simulator-flow/src/main/kotlin/org/opendc/simulator/flow/source/FlowSourceBarrier.kt b/opendc-simulator/opendc-simulator-flow/src/main/java/org/opendc/simulator/flow2/util/FlowTransforms.java index b3191ad3..428dbfca 100644 --- a/opendc-simulator/opendc-simulator-flow/src/main/kotlin/org/opendc/simulator/flow/source/FlowSourceBarrier.kt +++ b/opendc-simulator/opendc-simulator-flow/src/main/java/org/opendc/simulator/flow2/util/FlowTransforms.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2021 AtLarge Research + * Copyright (c) 2022 AtLarge Research * * Permission is hereby granted, free of charge, to any person obtaining a copy * of this software and associated documentation files (the "Software"), to deal @@ -20,33 +20,38 @@ * SOFTWARE. */ -package org.opendc.simulator.flow.source +package org.opendc.simulator.flow2.util; /** - * The [FlowSourceBarrier] is a barrier that allows multiple sources to wait for a select number of other sources to - * finish a pull, before proceeding its operation. + * A collection of common {@link FlowTransform} implementations. */ -public class FlowSourceBarrier(public val parties: Int) { - private var counter = 0 +public class FlowTransforms { + /** + * Prevent construction of this class. + */ + private FlowTransforms() {} /** - * Enter the barrier and determine whether the caller is the last to reach the barrier. - * - * @return `true` if the caller is the last to reach the barrier, `false` otherwise. + * Return a {@link FlowTransform} that forwards the flow rate unmodified. */ - public fun enter(): Boolean { - val last = ++counter == parties - if (last) { - counter = 0 - return true - } - return false + public static FlowTransform noop() { + return NoopFlowTransform.INSTANCE; } /** - * Reset the barrier. + * No-op implementation of a {@link FlowTransform}. */ - public fun reset() { - counter = 0 + private static final class NoopFlowTransform implements FlowTransform { + static final NoopFlowTransform INSTANCE = new NoopFlowTransform(); + + @Override + public float apply(float value) { + return value; + } + + @Override + public float applyInverse(float value) { + return value; + } } } diff --git a/opendc-simulator/opendc-simulator-flow/src/main/kotlin/org/opendc/simulator/flow/FlowConsumer.kt b/opendc-simulator/opendc-simulator-flow/src/main/kotlin/org/opendc/simulator/flow/FlowConsumer.kt deleted file mode 100644 index a49826f4..00000000 --- a/opendc-simulator/opendc-simulator-flow/src/main/kotlin/org/opendc/simulator/flow/FlowConsumer.kt +++ /dev/null @@ -1,131 +0,0 @@ -/* - * 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.flow - -import kotlinx.coroutines.suspendCancellableCoroutine -import kotlin.coroutines.resume -import kotlin.coroutines.resumeWithException - -/** - * A consumer of a [FlowSource]. - */ -public interface FlowConsumer { - /** - * A flag to indicate that the consumer is currently consuming a [FlowSource]. - */ - public val isActive: Boolean - - /** - * The flow capacity of this consumer. - */ - public val capacity: Double - - /** - * The current flow rate of the consumer. - */ - public val rate: Double - - /** - * The current flow demand. - */ - public val demand: Double - - /** - * The flow counters to track the flow metrics of the consumer. - */ - public val counters: FlowCounters - - /** - * Start consuming the specified [source]. - * - * @throws IllegalStateException if the consumer is already active. - */ - public fun startConsumer(source: FlowSource) - - /** - * Ask the consumer to pull its source. - * - * If the consumer is not active, this operation will be a no-op. - */ - public fun pull() - - /** - * Disconnect the consumer from its source. - * - * If the consumer is not active, this operation will be a no-op. - */ - public fun cancel() -} - -/** - * Consume the specified [source] and suspend execution until the source is fully consumed or failed. - */ -public suspend fun FlowConsumer.consume(source: FlowSource) { - return suspendCancellableCoroutine { cont -> - startConsumer(object : FlowSource { - override fun onStart(conn: FlowConnection, now: Long) { - try { - source.onStart(conn, now) - } catch (cause: Throwable) { - cont.resumeWithException(cause) - throw cause - } - } - - override fun onStop(conn: FlowConnection, now: Long) { - try { - source.onStop(conn, now) - - if (!cont.isCompleted) { - cont.resume(Unit) - } - } catch (cause: Throwable) { - cont.resumeWithException(cause) - throw cause - } - } - - override fun onPull(conn: FlowConnection, now: Long): Long { - return try { - source.onPull(conn, now) - } catch (cause: Throwable) { - cont.resumeWithException(cause) - throw cause - } - } - - override fun onConverge(conn: FlowConnection, now: Long) { - try { - source.onConverge(conn, now) - } catch (cause: Throwable) { - cont.resumeWithException(cause) - throw cause - } - } - - override fun toString(): String = "SuspendingFlowSource" - }) - - cont.invokeOnCancellation { cancel() } - } -} diff --git a/opendc-simulator/opendc-simulator-flow/src/main/kotlin/org/opendc/simulator/flow/FlowConsumerLogic.kt b/opendc-simulator/opendc-simulator-flow/src/main/kotlin/org/opendc/simulator/flow/FlowConsumerLogic.kt deleted file mode 100644 index 1d3adb10..00000000 --- a/opendc-simulator/opendc-simulator-flow/src/main/kotlin/org/opendc/simulator/flow/FlowConsumerLogic.kt +++ /dev/null @@ -1,57 +0,0 @@ -/* - * 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.flow - -/** - * A collection of callbacks associated with a [FlowConsumer]. - */ -public interface FlowConsumerLogic { - /** - * This method is invoked when a [FlowSource] changes the rate of flow to this consumer. - * - * @param ctx The context in which the provider runs. - * @param now The virtual timestamp in milliseconds at which the update is occurring. - * @param rate The requested processing rate of the source. - */ - public fun onPush(ctx: FlowConsumerContext, now: Long, rate: Double) {} - - /** - * This method is invoked when the flow graph has converged into a steady-state system. - * - * Make sure to enable [FlowConsumerContext.shouldSourceConverge] if you need this callback. By default, this method - * will not be invoked. - * - * @param ctx The context in which the provider runs. - * @param now The virtual timestamp in milliseconds at which the system converged. - */ - public fun onConverge(ctx: FlowConsumerContext, now: Long) {} - - /** - * This method is invoked when the [FlowSource] completed or failed. - * - * @param ctx The context in which the provider runs. - * @param now The virtual timestamp in milliseconds at which the provider finished. - * @param cause The cause of the failure or `null` if the source completed. - */ - public fun onFinish(ctx: FlowConsumerContext, now: Long, cause: Throwable?) {} -} diff --git a/opendc-simulator/opendc-simulator-flow/src/main/kotlin/org/opendc/simulator/flow/FlowCounters.kt b/opendc-simulator/opendc-simulator-flow/src/main/kotlin/org/opendc/simulator/flow/FlowCounters.kt deleted file mode 100644 index d8ad7978..00000000 --- a/opendc-simulator/opendc-simulator-flow/src/main/kotlin/org/opendc/simulator/flow/FlowCounters.kt +++ /dev/null @@ -1,48 +0,0 @@ -/* - * 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.flow - -/** - * An interface that tracks cumulative counts of the flow accumulation over a stage. - */ -public interface FlowCounters { - /** - * The accumulated flow that a source wanted to push over the connection. - */ - public val demand: Double - - /** - * The accumulated flow that was actually transferred over the connection. - */ - public val actual: Double - - /** - * The amount of capacity that was not utilized. - */ - public val remaining: Double - - /** - * Reset the flow counters. - */ - public fun reset() -} diff --git a/opendc-simulator/opendc-simulator-flow/src/main/kotlin/org/opendc/simulator/flow/FlowEngine.kt b/opendc-simulator/opendc-simulator-flow/src/main/kotlin/org/opendc/simulator/flow/FlowEngine.kt deleted file mode 100644 index 65224827..00000000 --- a/opendc-simulator/opendc-simulator-flow/src/main/kotlin/org/opendc/simulator/flow/FlowEngine.kt +++ /dev/null @@ -1,95 +0,0 @@ -/* - * 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.flow - -import org.opendc.simulator.flow.internal.FlowEngineImpl -import java.time.Clock -import kotlin.coroutines.CoroutineContext - -/** - * A [FlowEngine] is responsible for managing the interaction between [FlowSource]s and [FlowConsumer]s. - * - * The engine centralizes the scheduling logic of state updates of flow connections, allowing update propagation - * to happen more efficiently. and overall, reducing the work necessary to transition into a steady state. - */ -public interface FlowEngine { - /** - * The virtual [Clock] associated with this engine. - */ - public val clock: Clock - - /** - * Create a new [FlowConsumerContext] with the given [provider]. - * - * @param consumer The consumer logic. - * @param provider The logic of the resource provider. - */ - public fun newContext(consumer: FlowSource, provider: FlowConsumerLogic): FlowConsumerContext - - /** - * Start batching the execution of resource updates until [popBatch] is called. - * - * This method is useful if you want to propagate multiple resources updates (e.g., starting multiple CPUs - * simultaneously) in a single state update. - * - * Multiple calls to this method requires the same number of [popBatch] calls in order to properly flush the - * resource updates. This allows nested calls to [pushBatch], but might cause issues if [popBatch] is not called - * the same amount of times. To simplify batching, see [batch]. - */ - public fun pushBatch() - - /** - * Stop the batching of resource updates and run the interpreter on the batch. - * - * Note that method will only flush the event once the first call to [pushBatch] has received a [popBatch] call. - */ - public fun popBatch() - - public companion object { - /** - * Construct a new [FlowEngine] implementation. - * - * @param context The coroutine context to use. - * @param clock The virtual simulation clock. - */ - @JvmStatic - @JvmName("create") - public operator fun invoke(context: CoroutineContext, clock: Clock): FlowEngine { - return FlowEngineImpl(context, clock) - } - } -} - -/** - * Batch the execution of several interrupts into a single call. - * - * This method is useful if you want to propagate the start of multiple resources (e.g., CPUs) in a single update. - */ -public inline fun FlowEngine.batch(block: () -> Unit) { - try { - pushBatch() - block() - } finally { - popBatch() - } -} diff --git a/opendc-simulator/opendc-simulator-flow/src/main/kotlin/org/opendc/simulator/flow/FlowForwarder.kt b/opendc-simulator/opendc-simulator-flow/src/main/kotlin/org/opendc/simulator/flow/FlowForwarder.kt deleted file mode 100644 index 5202c252..00000000 --- a/opendc-simulator/opendc-simulator-flow/src/main/kotlin/org/opendc/simulator/flow/FlowForwarder.kt +++ /dev/null @@ -1,264 +0,0 @@ -/* - * 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.flow - -import mu.KotlinLogging -import org.opendc.simulator.flow.internal.D_MS_TO_S -import org.opendc.simulator.flow.internal.MutableFlowCounters - -/** - * The logging instance of this connection. - */ -private val logger = KotlinLogging.logger {} - -/** - * A class that acts as a [FlowSource] and [FlowConsumer] at the same time. - * - * @param engine The [FlowEngine] the forwarder runs in. - * @param listener The convergence lister to use. - * @param isCoupled A flag to indicate that the transformer will exit when the resource consumer exits. - */ -public class FlowForwarder( - private val engine: FlowEngine, - private val listener: FlowConvergenceListener? = null, - private val isCoupled: Boolean = false -) : FlowSource, FlowConsumer, AutoCloseable { - /** - * The delegate [FlowSource]. - */ - private var delegate: FlowSource? = null - - /** - * A flag to indicate that the delegate was started. - */ - private var hasDelegateStarted: Boolean = false - - /** - * The exposed [FlowConnection]. - */ - private val _ctx = object : FlowConnection { - override var shouldSourceConverge: Boolean = false - set(value) { - field = value - _innerCtx?.shouldSourceConverge = value - } - - override val capacity: Double - get() = _innerCtx?.capacity ?: 0.0 - - override val demand: Double - get() = _innerCtx?.demand ?: 0.0 - - override val rate: Double - get() = _innerCtx?.rate ?: 0.0 - - override fun pull() { - _innerCtx?.pull() - } - - override fun pull(now: Long) { - _innerCtx?.pull(now) - } - - override fun push(rate: Double) { - if (delegate == null) { - return - } - - _innerCtx?.push(rate) - _demand = rate - } - - override fun close() { - val delegate = delegate ?: return - val hasDelegateStarted = hasDelegateStarted - - // Warning: resumption of the continuation might change the entire state of the forwarder. Make sure we - // reset beforehand the existing state and check whether it has been updated afterwards - reset() - - if (hasDelegateStarted) { - val now = engine.clock.millis() - delegate.onStop(this, now) - } - } - } - - /** - * The [FlowConnection] in which the forwarder runs. - */ - private var _innerCtx: FlowConnection? = null - - override val isActive: Boolean - get() = delegate != null - - override val capacity: Double - get() = _ctx.capacity - - override val rate: Double - get() = _ctx.rate - - override val demand: Double - get() = _ctx.demand - - override val counters: FlowCounters - get() = _counters - private val _counters = MutableFlowCounters() - - override fun startConsumer(source: FlowSource) { - check(delegate == null) { "Forwarder already active" } - - delegate = source - - // Pull to replace the source - pull() - } - - override fun pull() { - _ctx.pull() - } - - override fun cancel() { - _ctx.close() - } - - override fun close() { - val ctx = _innerCtx - - if (ctx != null) { - this._innerCtx = null - ctx.pull() - } - } - - override fun onStart(conn: FlowConnection, now: Long) { - _innerCtx = conn - - if (listener != null || _ctx.shouldSourceConverge) { - conn.shouldSourceConverge = true - } - } - - override fun onStop(conn: FlowConnection, now: Long) { - _innerCtx = null - - val delegate = delegate - if (delegate != null) { - reset() - - try { - delegate.onStop(this._ctx, now) - } catch (cause: Throwable) { - logger.error(cause) { "Uncaught exception" } - } - } - } - - override fun onPull(conn: FlowConnection, now: Long): Long { - val delegate = delegate - - if (!hasDelegateStarted) { - start() - } - - updateCounters(conn, now) - - return try { - delegate?.onPull(_ctx, now) ?: Long.MAX_VALUE - } catch (cause: Throwable) { - logger.error(cause) { "Uncaught exception" } - - reset() - Long.MAX_VALUE - } - } - - override fun onConverge(conn: FlowConnection, now: Long) { - try { - delegate?.onConverge(this._ctx, now) - listener?.onConverge(now) - } catch (cause: Throwable) { - logger.error(cause) { "Uncaught exception" } - - _innerCtx = null - reset() - } - } - - /** - * Start the delegate. - */ - private fun start() { - val delegate = delegate ?: return - - try { - val now = engine.clock.millis() - delegate.onStart(_ctx, now) - hasDelegateStarted = true - _lastUpdate = now - } catch (cause: Throwable) { - logger.error(cause) { "Uncaught exception" } - reset() - } - } - - /** - * Reset the delegate. - */ - private fun reset() { - if (isCoupled) { - _innerCtx?.close() - } else { - _innerCtx?.push(0.0) - } - - delegate = null - hasDelegateStarted = false - } - - /** - * The requested flow rate. - */ - private var _demand: Double = 0.0 - private var _lastUpdate = 0L - - /** - * Update the flow counters for the transformer. - */ - private fun updateCounters(ctx: FlowConnection, now: Long) { - val lastUpdate = _lastUpdate - _lastUpdate = now - val delta = now - lastUpdate - if (delta <= 0) { - return - } - - val counters = _counters - val deltaS = delta * D_MS_TO_S - val total = ctx.capacity * deltaS - val work = _demand * deltaS - val actualWork = ctx.rate * deltaS - - counters.increment(work, actualWork, (total - actualWork)) - } -} diff --git a/opendc-simulator/opendc-simulator-flow/src/main/kotlin/org/opendc/simulator/flow/FlowMapper.kt b/opendc-simulator/opendc-simulator-flow/src/main/kotlin/org/opendc/simulator/flow/FlowMapper.kt deleted file mode 100644 index af702701..00000000 --- a/opendc-simulator/opendc-simulator-flow/src/main/kotlin/org/opendc/simulator/flow/FlowMapper.kt +++ /dev/null @@ -1,75 +0,0 @@ -/* - * 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.flow - -/** - * A [FlowConsumer] that maps the pushed flow through [transform]. - * - * @param source The source of the flow. - * @param transform The method to transform the flow. - */ -public class FlowMapper( - private val source: FlowSource, - private val transform: (FlowConnection, Double) -> Double -) : FlowSource { - - /** - * The current active connection. - */ - private var _conn: Connection? = null - - override fun onStart(conn: FlowConnection, now: Long) { - check(_conn == null) { "Concurrent access" } - val delegate = Connection(conn, transform) - _conn = delegate - source.onStart(delegate, now) - } - - override fun onStop(conn: FlowConnection, now: Long) { - val delegate = checkNotNull(_conn) { "Invariant violation" } - _conn = null - source.onStop(delegate, now) - } - - override fun onPull(conn: FlowConnection, now: Long): Long { - val delegate = checkNotNull(_conn) { "Invariant violation" } - return source.onPull(delegate, now) - } - - override fun onConverge(conn: FlowConnection, now: Long) { - val delegate = _conn ?: return - source.onConverge(delegate, now) - } - - /** - * The wrapper [FlowConnection] that is used to transform the flow. - */ - private class Connection( - private val delegate: FlowConnection, - private val transform: (FlowConnection, Double) -> Double - ) : FlowConnection by delegate { - override fun push(rate: Double) { - delegate.push(transform(this, rate)) - } - } -} diff --git a/opendc-simulator/opendc-simulator-flow/src/main/kotlin/org/opendc/simulator/flow/FlowSink.kt b/opendc-simulator/opendc-simulator-flow/src/main/kotlin/org/opendc/simulator/flow/FlowSink.kt deleted file mode 100644 index ee8cd739..00000000 --- a/opendc-simulator/opendc-simulator-flow/src/main/kotlin/org/opendc/simulator/flow/FlowSink.kt +++ /dev/null @@ -1,160 +0,0 @@ -/* - * 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.flow - -import org.opendc.simulator.flow.internal.D_MS_TO_S -import org.opendc.simulator.flow.internal.MutableFlowCounters - -/** - * A [FlowSink] represents a sink with a fixed capacity. - * - * @param initialCapacity The initial capacity of the resource. - * @param engine The engine that is used for driving the flow simulation. - * @param parent The parent flow system. - */ -public class FlowSink( - private val engine: FlowEngine, - initialCapacity: Double, - private val parent: FlowConvergenceListener? = null -) : FlowConsumer { - /** - * A flag to indicate that the flow consumer is active. - */ - public override val isActive: Boolean - get() = _ctx != null - - /** - * The capacity of the consumer. - */ - public override var capacity: Double = initialCapacity - set(value) { - field = value - _ctx?.capacity = value - } - - /** - * The current processing rate of the consumer. - */ - public override val rate: Double - get() = _ctx?.rate ?: 0.0 - - /** - * The flow processing rate demand at this instant. - */ - public override val demand: Double - get() = _ctx?.demand ?: 0.0 - - /** - * The flow counters to track the flow metrics of the consumer. - */ - public override val counters: FlowCounters - get() = _counters - private val _counters = MutableFlowCounters() - - /** - * The current active [FlowConsumerLogic] of this sink. - */ - private var _ctx: FlowConsumerContext? = null - - override fun startConsumer(source: FlowSource) { - check(_ctx == null) { "Consumer is in invalid state" } - - val ctx = engine.newContext(source, Logic(parent, _counters)) - _ctx = ctx - - ctx.capacity = capacity - if (parent != null) { - ctx.shouldConsumerConverge = true - } - - ctx.start() - } - - override fun pull() { - _ctx?.pull() - } - - override fun cancel() { - _ctx?.close() - } - - override fun toString(): String = "FlowSink[capacity=$capacity]" - - /** - * [FlowConsumerLogic] of a sink. - */ - private inner class Logic(private val parent: FlowConvergenceListener?, private val counters: MutableFlowCounters) : FlowConsumerLogic { - - override fun onPush( - ctx: FlowConsumerContext, - now: Long, - rate: Double - ) { - updateCounters(ctx, now, rate, ctx.capacity) - } - - override fun onFinish(ctx: FlowConsumerContext, now: Long, cause: Throwable?) { - updateCounters(ctx, now, 0.0, 0.0) - - _ctx = null - } - - override fun onConverge(ctx: FlowConsumerContext, now: Long) { - parent?.onConverge(now) - } - - /** - * The previous demand and capacity for the consumer. - */ - private val _previous = DoubleArray(2) - private var _previousUpdate = Long.MAX_VALUE - - /** - * Update the counters of the flow consumer. - */ - private fun updateCounters(ctx: FlowConnection, now: Long, nextDemand: Double, nextCapacity: Double) { - val previousUpdate = _previousUpdate - _previousUpdate = now - val delta = now - previousUpdate - - val counters = counters - val previous = _previous - val demand = previous[0] - val capacity = previous[1] - - previous[0] = nextDemand - previous[1] = nextCapacity - - if (delta <= 0) { - return - } - - val deltaS = delta * D_MS_TO_S - val total = demand * deltaS - val work = capacity * deltaS - val actualWork = ctx.rate * deltaS - - counters.increment(work, actualWork, (total - actualWork)) - } - } -} diff --git a/opendc-simulator/opendc-simulator-flow/src/main/kotlin/org/opendc/simulator/flow/FlowSource.kt b/opendc-simulator/opendc-simulator-flow/src/main/kotlin/org/opendc/simulator/flow/FlowSource.kt deleted file mode 100644 index a48ac18e..00000000 --- a/opendc-simulator/opendc-simulator-flow/src/main/kotlin/org/opendc/simulator/flow/FlowSource.kt +++ /dev/null @@ -1,67 +0,0 @@ -/* - * 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.flow - -/** - * A source of flow that is consumed by a [FlowConsumer]. - * - * Implementations of this interface should be considered stateful and must be assumed not to be re-usable - * (concurrently) for multiple [FlowConsumer]s, unless explicitly said otherwise. - */ -public interface FlowSource { - /** - * This method is invoked when the source is started. - * - * @param conn The connection between the source and consumer. - * @param now The virtual timestamp in milliseconds at which the provider finished. - */ - public fun onStart(conn: FlowConnection, now: Long) {} - - /** - * This method is invoked when the source is finished. - * - * @param conn The connection between the source and consumer. - * @param now The virtual timestamp in milliseconds at which the source finished. - */ - public fun onStop(conn: FlowConnection, now: Long) {} - - /** - * This method is invoked when the source is pulled. - * - * @param conn The connection between the source and consumer. - * @param now The virtual timestamp in milliseconds at which the pull is occurring. - * @return The duration after which the resource consumer should be pulled again. - */ - public fun onPull(conn: FlowConnection, now: Long): Long - - /** - * This method is invoked when the flow graph has converged into a steady-state system. - * - * Make sure to enable [FlowConnection.shouldSourceConverge] if you need this callback. By default, this method - * will not be invoked. - * - * @param conn The connection between the source and consumer. - * @param now The virtual timestamp in milliseconds at which the system converged. - */ - public fun onConverge(conn: FlowConnection, now: Long) {} -} diff --git a/opendc-simulator/opendc-simulator-flow/src/main/kotlin/org/opendc/simulator/flow/internal/Flags.kt b/opendc-simulator/opendc-simulator-flow/src/main/kotlin/org/opendc/simulator/flow/internal/Flags.kt deleted file mode 100644 index 97d56fff..00000000 --- a/opendc-simulator/opendc-simulator-flow/src/main/kotlin/org/opendc/simulator/flow/internal/Flags.kt +++ /dev/null @@ -1,44 +0,0 @@ -/* - * 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.flow.internal - -/** - * States of the flow connection. - */ -internal const val ConnPending = 0 // Connection is pending and the consumer is waiting to consume the source -internal const val ConnActive = 1 // Connection is active and the source is currently being consumed -internal const val ConnClosed = 2 // Connection is closed and source cannot be consumed through this connection anymore -internal const val ConnState = 0b11 // Mask for accessing the state of the flow connection - -/** - * Flags of the flow connection - */ -internal const val ConnPulled = 1 shl 2 // The source should be pulled -internal const val ConnPushed = 1 shl 3 // The source has pushed a value -internal const val ConnClose = 1 shl 4 // The connection should be closed -internal const val ConnUpdateActive = 1 shl 5 // An update for the connection is active -internal const val ConnUpdatePending = 1 shl 6 // An (immediate) update of the connection is pending -internal const val ConnConvergePending = 1 shl 7 // Indication that a convergence is already pending -internal const val ConnConvergeSource = 1 shl 8 // Enable convergence of the source -internal const val ConnConvergeConsumer = 1 shl 9 // Enable convergence of the consumer -internal const val ConnDisableTimers = 1 shl 10 // Disable timers for the source diff --git a/opendc-simulator/opendc-simulator-flow/src/main/kotlin/org/opendc/simulator/flow/internal/FlowConsumerContextImpl.kt b/opendc-simulator/opendc-simulator-flow/src/main/kotlin/org/opendc/simulator/flow/internal/FlowConsumerContextImpl.kt deleted file mode 100644 index fba3af5f..00000000 --- a/opendc-simulator/opendc-simulator-flow/src/main/kotlin/org/opendc/simulator/flow/internal/FlowConsumerContextImpl.kt +++ /dev/null @@ -1,436 +0,0 @@ -/* - * 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.flow.internal - -import mu.KotlinLogging -import org.opendc.simulator.flow.FlowConsumerContext -import org.opendc.simulator.flow.FlowConsumerLogic -import org.opendc.simulator.flow.FlowSource -import org.opendc.simulator.flow.batch -import java.util.ArrayDeque -import kotlin.math.min - -/** - * The logging instance of this connection. - */ -private val logger = KotlinLogging.logger {} - -/** - * Implementation of a [FlowConnection] managing the communication between flow sources and consumers. - */ -internal class FlowConsumerContextImpl( - private val engine: FlowEngineImpl, - private val source: FlowSource, - private val logic: FlowConsumerLogic -) : FlowConsumerContext { - /** - * The capacity of the connection. - */ - override var capacity: Double - get() = _capacity - set(value) { - val oldValue = _capacity - - // Only changes will be propagated - if (value != oldValue) { - _capacity = value - pull() - } - } - private var _capacity: Double = 0.0 - - /** - * The current processing rate of the connection. - */ - override val rate: Double - get() = _rate - private var _rate = 0.0 - - /** - * The current flow processing demand. - */ - override val demand: Double - get() = _demand - private var _demand: Double = 0.0 // The current (pending) demand of the source - - /** - * The deadline of the source. - */ - override val deadline: Long - get() = _deadline - private var _deadline: Long = Long.MAX_VALUE // The deadline of the source's timer - - /** - * Flags to control the convergence of the consumer and source. - */ - override var shouldSourceConverge: Boolean - get() = _flags and ConnConvergeSource == ConnConvergeSource - set(value) { - _flags = - if (value) { - _flags or ConnConvergeSource - } else { - _flags and ConnConvergeSource.inv() - } - } - override var shouldConsumerConverge: Boolean - get() = _flags and ConnConvergeConsumer == ConnConvergeConsumer - set(value) { - _flags = - if (value) { - _flags or ConnConvergeConsumer - } else { - _flags and ConnConvergeConsumer.inv() - } - } - - /** - * Flag to control the timers on the [FlowSource] - */ - override var enableTimers: Boolean - get() = _flags and ConnDisableTimers != ConnDisableTimers - set(value) { - _flags = - if (!value) { - _flags or ConnDisableTimers - } else { - _flags and ConnDisableTimers.inv() - } - } - - /** - * The clock to track simulation time. - */ - private val _clock = engine.clock - - /** - * The flags of the flow connection, indicating certain actions. - */ - private var _flags: Int = 0 - - /** - * The timers at which the context is scheduled to be interrupted. - */ - private var _timer: Long = Long.MAX_VALUE - private val _pendingTimers: ArrayDeque<Long> = ArrayDeque(5) - - override fun start() { - check(_flags and ConnState == ConnPending) { "Consumer is already started" } - engine.batch { - val now = _clock.millis() - source.onStart(this, now) - - // Mark the connection as active and pulled - val newFlags = (_flags and ConnState.inv()) or ConnActive or ConnPulled - scheduleImmediate(now, newFlags) - } - } - - override fun close() { - val flags = _flags - if (flags and ConnState == ConnClosed) { - return - } - - // Toggle the close bit. In case no update is active, schedule a new update. - if (flags and ConnUpdateActive == 0) { - val now = _clock.millis() - scheduleImmediate(now, flags or ConnClose) - } else { - _flags = flags or ConnClose - } - } - - override fun pull(now: Long) { - val flags = _flags - if (flags and ConnState != ConnActive) { - return - } - - // Mark connection as pulled - scheduleImmediate(now, flags or ConnPulled) - } - - override fun pull() { - pull(_clock.millis()) - } - - override fun pullSync(now: Long) { - val flags = _flags - - // Do not attempt to flush the connection if the connection is closed or an update is already active - if (flags and ConnState != ConnActive || flags and ConnUpdateActive != 0) { - return - } - - if (flags and (ConnPulled or ConnPushed) != 0 || _deadline == now) { - engine.scheduleSync(now, this) - } - } - - override fun push(rate: Double) { - if (_demand == rate) { - return - } - - _demand = rate - - val flags = _flags - - if (flags and ConnUpdateActive != 0) { - // If an update is active, it will already get picked up at the end of the update - _flags = flags or ConnPushed - } else { - // Invalidate only if no update is active - scheduleImmediate(_clock.millis(), flags or ConnPushed) - } - } - - /** - * Update the state of the flow connection. - * - * @param now The current virtual timestamp. - * @param visited The queue of connections that have been visited during the cycle. - * @param timerQueue The queue of all pending timers. - * @param isImmediate A flag to indicate that this invocation is an immediate update or a delayed update. - */ - fun doUpdate( - now: Long, - visited: FlowDeque, - timerQueue: FlowTimerQueue, - isImmediate: Boolean - ) { - var flags = _flags - - // Precondition: The flow connection must be active - if (flags and ConnState != ConnActive) { - return - } - - val deadline = _deadline - val reachedDeadline = deadline == now - var newDeadline: Long - var hasUpdated = false - - try { - // Pull the source if (1) `pull` is called or (2) the timer of the source has expired - newDeadline = if (flags and ConnPulled != 0 || reachedDeadline) { - // Update state before calling into the outside world, so it observes a consistent state - _flags = (flags and ConnPulled.inv()) or ConnUpdateActive - hasUpdated = true - - val duration = source.onPull(this, now) - - // IMPORTANT: Re-fetch the flags after the callback might have changed those - flags = _flags - - if (duration != Long.MAX_VALUE) { - now + duration - } else { - duration - } - } else { - deadline - } - - // Make the new deadline available for the consumer if it has changed - if (newDeadline != deadline) { - _deadline = newDeadline - } - - // Push to the consumer if the rate of the source has changed (after a call to `push`) - if (flags and ConnPushed != 0) { - // Update state before calling into the outside world, so it observes a consistent state - _flags = (flags and ConnPushed.inv()) or ConnUpdateActive - hasUpdated = true - - logic.onPush(this, now, _demand) - - // IMPORTANT: Re-fetch the flags after the callback might have changed those - flags = _flags - } - - // Check whether the source or consumer have tried to close the connection - if (flags and ConnClose != 0) { - hasUpdated = true - - // The source has called [FlowConnection.close], so clean up the connection - doStopSource(now) - - // IMPORTANT: Re-fetch the flags after the callback might have changed those - // We now also mark the connection as closed - flags = (_flags and ConnState.inv()) or ConnClosed - - _demand = 0.0 - newDeadline = Long.MAX_VALUE - } - } catch (cause: Throwable) { - hasUpdated = true - - // Clean up the connection - doFailSource(now, cause) - - // Mark the connection as closed - flags = (flags and ConnState.inv()) or ConnClosed - - _demand = 0.0 - newDeadline = Long.MAX_VALUE - } - - // Check whether the connection needs to be added to the visited queue. This is the case when: - // (1) An update was performed (either a push or a pull) - // (2) Either the source or consumer want to converge, and - // (3) Convergence is not already pending (ConnConvergePending) - if (hasUpdated && flags and (ConnConvergeSource or ConnConvergeConsumer) != 0 && flags and ConnConvergePending == 0) { - visited.add(this) - flags = flags or ConnConvergePending - } - - // Compute the new flow rate of the connection - // Note: _demand might be changed by [logic.onConsume], so we must re-fetch the value - _rate = min(_capacity, _demand) - - // Indicate that no update is active anymore and flush the flags - _flags = flags and ConnUpdateActive.inv() and ConnUpdatePending.inv() - - val pendingTimers = _pendingTimers - - // Prune the head timer if this is a delayed update - val timer = if (!isImmediate) { - // Invariant: Any pending timer should only point to a future timestamp - val timer = pendingTimers.poll() ?: Long.MAX_VALUE - _timer = timer - timer - } else { - _timer - } - - // Check whether we need to schedule a new timer for this connection. That is the case when: - // (1) The deadline is valid (not the maximum value) - // (2) The connection is active - // (3) Timers are not disabled for the source - // (4) The current active timer for the connection points to a later deadline - if (newDeadline == Long.MAX_VALUE || - flags and ConnState != ConnActive || - flags and ConnDisableTimers != 0 || - (timer != Long.MAX_VALUE && newDeadline >= timer) - ) { - // Ignore any deadline scheduled at the maximum value - // This indicates that the source does not want to register a timer - return - } - - // Construct a timer for the new deadline and add it to the global queue of timers - _timer = newDeadline - timerQueue.add(this, newDeadline) - - // Slow-path: a timer already exists for this connection, so add it to the queue of pending timers - if (timer != Long.MAX_VALUE) { - pendingTimers.addFirst(timer) - } - } - - /** - * This method is invoked when the system converges into a steady state. - */ - fun onConverge(now: Long) { - try { - val flags = _flags - - // The connection is converging now, so unset the convergence pending flag - _flags = flags and ConnConvergePending.inv() - - // Call the source converge callback if it has enabled convergence - if (flags and ConnConvergeSource != 0) { - source.onConverge(this, now) - } - - // Call the consumer callback if it has enabled convergence - if (flags and ConnConvergeConsumer != 0) { - logic.onConverge(this, now) - } - } catch (cause: Throwable) { - // Invoke the finish callbacks - doFailSource(now, cause) - - // Mark the connection as closed - _flags = (_flags and ConnState.inv()) or ConnClosed - _demand = 0.0 - _deadline = Long.MAX_VALUE - } - } - - override fun toString(): String = "FlowConsumerContextImpl[capacity=$capacity,rate=$_rate]" - - /** - * Stop the [FlowSource]. - */ - private fun doStopSource(now: Long) { - try { - source.onStop(this, now) - doFinishConsumer(now, null) - } catch (cause: Throwable) { - doFinishConsumer(now, cause) - } - } - - /** - * Fail the [FlowSource]. - */ - private fun doFailSource(now: Long, cause: Throwable) { - try { - source.onStop(this, now) - } catch (e: Throwable) { - e.addSuppressed(cause) - doFinishConsumer(now, e) - } - } - - /** - * Finish the consumer. - */ - private fun doFinishConsumer(now: Long, cause: Throwable?) { - try { - logic.onFinish(this, now, cause) - } catch (e: Throwable) { - e.addSuppressed(cause) - logger.error(e) { "Uncaught exception" } - } - } - - /** - * Schedule an immediate update for this connection. - */ - private fun scheduleImmediate(now: Long, flags: Int) { - // In case an immediate update is already scheduled, no need to do anything - if (flags and ConnUpdatePending != 0) { - _flags = flags - return - } - - // Mark the connection that there is an update pending - _flags = flags or ConnUpdatePending - - engine.scheduleImmediate(now, this) - } -} diff --git a/opendc-simulator/opendc-simulator-flow/src/main/kotlin/org/opendc/simulator/flow/internal/FlowDeque.kt b/opendc-simulator/opendc-simulator-flow/src/main/kotlin/org/opendc/simulator/flow/internal/FlowDeque.kt deleted file mode 100644 index 403a9aec..00000000 --- a/opendc-simulator/opendc-simulator-flow/src/main/kotlin/org/opendc/simulator/flow/internal/FlowDeque.kt +++ /dev/null @@ -1,119 +0,0 @@ -/* - * 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.flow.internal - -import java.util.ArrayDeque - -/** - * A specialized [ArrayDeque] that tracks the [FlowConsumerContextImpl] instances that have updated in an interpreter - * cycle. - * - * By using a specialized class, we reduce the overhead caused by type-erasure. - */ -internal class FlowDeque(initialCapacity: Int = 256) { - /** - * The array of elements in the queue. - */ - private var _elements: Array<FlowConsumerContextImpl?> = arrayOfNulls(initialCapacity) - private var _head = 0 - private var _tail = 0 - - /** - * Determine whether this queue is not empty. - */ - fun isNotEmpty(): Boolean { - return _head != _tail - } - - /** - * Add the specified [ctx] to the queue. - */ - fun add(ctx: FlowConsumerContextImpl) { - val es = _elements - var tail = _tail - - es[tail] = ctx - - tail = inc(tail, es.size) - _tail = tail - - if (_head == tail) { - doubleCapacity() - } - } - - /** - * Remove a [FlowConsumerContextImpl] from the queue or `null` if the queue is empty. - */ - fun poll(): FlowConsumerContextImpl? { - val es = _elements - val head = _head - val ctx = es[head] - - if (ctx != null) { - es[head] = null - _head = inc(head, es.size) - } - - return ctx - } - - /** - * Clear the queue. - */ - fun clear() { - _elements.fill(null) - _head = 0 - _tail = 0 - } - - private fun inc(i: Int, modulus: Int): Int { - var x = i - if (++x >= modulus) { - x = 0 - } - return x - } - - /** - * Doubles the capacity of this deque - */ - private fun doubleCapacity() { - assert(_head == _tail) - val p = _head - val n = _elements.size - val r = n - p // number of elements to the right of p - - val newCapacity = n shl 1 - check(newCapacity >= 0) { "Sorry, deque too big" } - - val a = arrayOfNulls<FlowConsumerContextImpl>(newCapacity) - - _elements.copyInto(a, 0, p, n) - _elements.copyInto(a, r, 0, p) - - _elements = a - _head = 0 - _tail = n - } -} diff --git a/opendc-simulator/opendc-simulator-flow/src/main/kotlin/org/opendc/simulator/flow/internal/FlowEngineImpl.kt b/opendc-simulator/opendc-simulator-flow/src/main/kotlin/org/opendc/simulator/flow/internal/FlowEngineImpl.kt deleted file mode 100644 index 6fd1ef31..00000000 --- a/opendc-simulator/opendc-simulator-flow/src/main/kotlin/org/opendc/simulator/flow/internal/FlowEngineImpl.kt +++ /dev/null @@ -1,218 +0,0 @@ -/* - * 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.flow.internal - -import kotlinx.coroutines.Delay -import kotlinx.coroutines.DisposableHandle -import kotlinx.coroutines.InternalCoroutinesApi -import kotlinx.coroutines.Runnable -import org.opendc.simulator.flow.FlowConsumerContext -import org.opendc.simulator.flow.FlowConsumerLogic -import org.opendc.simulator.flow.FlowEngine -import org.opendc.simulator.flow.FlowSource -import java.time.Clock -import java.util.ArrayDeque -import kotlin.coroutines.ContinuationInterceptor -import kotlin.coroutines.CoroutineContext - -/** - * Internal implementation of the [FlowEngine] interface. - * - * @param context The coroutine context to use. - * @param clock The virtual simulation clock. - */ -internal class FlowEngineImpl(private val context: CoroutineContext, clock: Clock) : FlowEngine, Runnable { - /** - * The [Delay] instance that provides scheduled execution of [Runnable]s. - */ - @OptIn(InternalCoroutinesApi::class) - private val delay = requireNotNull(context[ContinuationInterceptor] as? Delay) { "Invalid CoroutineDispatcher: no delay implementation" } - - /** - * The queue of connection updates that are scheduled for immediate execution. - */ - private val queue = FlowDeque() - - /** - * A priority queue containing the connection updates to be scheduled in the future. - */ - private val futureQueue = FlowTimerQueue() - - /** - * The stack of engine invocations to occur in the future. - */ - private val futureInvocations = ArrayDeque<Invocation>() - - /** - * The systems that have been visited during the engine cycle. - */ - private val visited = FlowDeque() - - /** - * The index in the batch stack. - */ - private var batchIndex = 0 - - /** - * The virtual [Clock] of this engine. - */ - override val clock: Clock - get() = _clock - private val _clock: Clock = clock - - /** - * Update the specified [ctx] synchronously. - */ - fun scheduleSync(now: Long, ctx: FlowConsumerContextImpl) { - ctx.doUpdate(now, visited, futureQueue, isImmediate = true) - - // In-case the engine is already running in the call-stack, return immediately. The changes will be picked - // up by the active engine. - if (batchIndex > 0) { - return - } - - doRunEngine(now) - } - - /** - * Enqueue the specified [ctx] to be updated immediately during the active engine cycle. - * - * This method should be used when the state of a flow context is invalidated/interrupted and needs to be - * re-computed. In case no engine is currently active, the engine will be started. - */ - fun scheduleImmediate(now: Long, ctx: FlowConsumerContextImpl) { - queue.add(ctx) - - // In-case the engine is already running in the call-stack, return immediately. The changes will be picked - // up by the active engine. - if (batchIndex > 0) { - return - } - - doRunEngine(now) - } - - override fun newContext(consumer: FlowSource, provider: FlowConsumerLogic): FlowConsumerContext = FlowConsumerContextImpl(this, consumer, provider) - - override fun pushBatch() { - batchIndex++ - } - - override fun popBatch() { - try { - // Flush the work if the engine is not already running - if (batchIndex == 1 && queue.isNotEmpty()) { - doRunEngine(_clock.millis()) - } - } finally { - batchIndex-- - } - } - - /* Runnable */ - override fun run() { - val invocation = futureInvocations.poll() // Clear invocation from future invocation queue - doRunEngine(invocation.timestamp) - } - - /** - * Run all the enqueued actions for the specified [timestamp][now]. - */ - private fun doRunEngine(now: Long) { - val queue = queue - val futureQueue = futureQueue - val futureInvocations = futureInvocations - val visited = visited - - try { - // Increment batch index so synchronous calls will not launch concurrent engine invocations - batchIndex++ - - // Execute all scheduled updates at current timestamp - while (true) { - val ctx = futureQueue.poll(now) ?: break - ctx.doUpdate(now, visited, futureQueue, isImmediate = false) - } - - // Repeat execution of all immediate updates until the system has converged to a steady-state - // We have to take into account that the onConverge callback can also trigger new actions. - do { - // Execute all immediate updates - while (true) { - val ctx = queue.poll() ?: break - ctx.doUpdate(now, visited, futureQueue, isImmediate = true) - } - - while (true) { - val ctx = visited.poll() ?: break - ctx.onConverge(now) - } - } while (queue.isNotEmpty()) - } finally { - // Decrement batch index to indicate no engine is active at the moment - batchIndex-- - } - - // Schedule an engine invocation for the next update to occur. - val headDeadline = futureQueue.peekDeadline() - if (headDeadline != Long.MAX_VALUE) { - trySchedule(now, futureInvocations, headDeadline) - } - } - - /** - * Try to schedule an engine invocation at the specified [target]. - * - * @param now The current virtual timestamp. - * @param target The virtual timestamp at which the engine invocation should happen. - * @param scheduled The queue of scheduled invocations. - */ - private fun trySchedule(now: Long, scheduled: ArrayDeque<Invocation>, target: Long) { - val head = scheduled.peek() - - // Only schedule a new scheduler invocation in case the target is earlier than all other pending - // scheduler invocations - if (head == null || target < head.timestamp) { - @OptIn(InternalCoroutinesApi::class) - val handle = delay.invokeOnTimeout(target - now, this, context) - scheduled.addFirst(Invocation(target, handle)) - } - } - - /** - * A future engine invocation. - * - * This class is used to keep track of the future engine invocations created using the [Delay] instance. In case - * the invocation is not needed anymore, it can be cancelled via [cancel]. - */ - private class Invocation( - @JvmField val timestamp: Long, - @JvmField val handle: DisposableHandle - ) { - /** - * Cancel the engine invocation. - */ - fun cancel() = handle.dispose() - } -} diff --git a/opendc-simulator/opendc-simulator-flow/src/main/kotlin/org/opendc/simulator/flow/internal/FlowTimerQueue.kt b/opendc-simulator/opendc-simulator-flow/src/main/kotlin/org/opendc/simulator/flow/internal/FlowTimerQueue.kt deleted file mode 100644 index 47061a91..00000000 --- a/opendc-simulator/opendc-simulator-flow/src/main/kotlin/org/opendc/simulator/flow/internal/FlowTimerQueue.kt +++ /dev/null @@ -1,200 +0,0 @@ -/* - * 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.flow.internal - -/** - * Specialized priority queue for flow timers. - * - * By using a specialized priority queue, we reduce the overhead caused by the default priority queue implementation - * being generic. - */ -internal class FlowTimerQueue(initialCapacity: Int = 256) { - /** - * The binary heap of deadlines. - */ - private var _deadlines = LongArray(initialCapacity) { Long.MIN_VALUE } - - /** - * The binary heap of [FlowConsumerContextImpl]s. - */ - private var _pending = arrayOfNulls<FlowConsumerContextImpl>(initialCapacity) - - /** - * The number of elements in the priority queue. - */ - private var size = 0 - - /** - * Register a timer for [ctx] with [deadline]. - */ - fun add(ctx: FlowConsumerContextImpl, deadline: Long) { - val i = size - var deadlines = _deadlines - if (i >= deadlines.size) { - grow() - // Re-fetch the resized array - deadlines = _deadlines - } - - siftUp(deadlines, _pending, i, ctx, deadline) - - size = i + 1 - } - - /** - * Update all pending [FlowConsumerContextImpl]s at the timestamp [now]. - */ - fun poll(now: Long): FlowConsumerContextImpl? { - if (size == 0) { - return null - } - - val deadlines = _deadlines - val deadline = deadlines[0] - - if (now < deadline) { - return null - } - - val pending = _pending - val res = pending[0] - val s = --size - - val nextDeadline = deadlines[s] - val next = pending[s]!! - - // Clear the last element of the queue - pending[s] = null - deadlines[s] = Long.MIN_VALUE - - if (s != 0) { - siftDown(deadlines, pending, next, nextDeadline) - } - - return res - } - - /** - * Find the earliest deadline in the queue. - */ - fun peekDeadline(): Long { - return if (size == 0) Long.MAX_VALUE else _deadlines[0] - } - - /** - * Increases the capacity of the array. - */ - private fun grow() { - val oldCapacity = _deadlines.size - // Double size if small; else grow by 50% - val newCapacity = oldCapacity + if (oldCapacity < 64) oldCapacity + 2 else oldCapacity shr 1 - - _deadlines = _deadlines.copyOf(newCapacity) - _pending = _pending.copyOf(newCapacity) - } - - /** - * Insert item [ctx] at position [pos], maintaining heap invariant by promoting [ctx] up the tree until it is - * greater than or equal to its parent, or is the root. - * - * @param deadlines The heap of deadlines. - * @param pending The heap of contexts. - * @param pos The position to fill. - * @param ctx The [FlowConsumerContextImpl] to insert. - * @param deadline The deadline of the context. - */ - private fun siftUp( - deadlines: LongArray, - pending: Array<FlowConsumerContextImpl?>, - pos: Int, - ctx: FlowConsumerContextImpl, - deadline: Long - ) { - var k = pos - - while (k > 0) { - val parent = (k - 1) ushr 1 - val parentDeadline = deadlines[parent] - - if (deadline >= parentDeadline) { - break - } - - deadlines[k] = parentDeadline - pending[k] = pending[parent] - - k = parent - } - - deadlines[k] = deadline - pending[k] = ctx - } - - /** - * Inserts [ctx] at the top, maintaining heap invariant by demoting [ctx] down the tree repeatedly until it - * is less than or equal to its children or is a leaf. - * - * @param deadlines The heap of deadlines. - * @param pending The heap of contexts. - * @param ctx The [FlowConsumerContextImpl] to insert. - * @param deadline The deadline of the context. - */ - private fun siftDown( - deadlines: LongArray, - pending: Array<FlowConsumerContextImpl?>, - ctx: FlowConsumerContextImpl, - deadline: Long - ) { - var k = 0 - val size = size - val half = size ushr 1 - - while (k < half) { - var child = (k shl 1) + 1 - - var childDeadline = deadlines[child] - val right = child + 1 - - if (right < size) { - val rightDeadline = deadlines[right] - - if (childDeadline > rightDeadline) { - child = right - childDeadline = rightDeadline - } - } - - if (deadline <= childDeadline) { - break - } - - deadlines[k] = childDeadline - pending[k] = pending[child] - - k = child - } - - deadlines[k] = deadline - pending[k] = ctx - } -} diff --git a/opendc-simulator/opendc-simulator-flow/src/main/kotlin/org/opendc/simulator/flow/internal/MutableFlowCounters.kt b/opendc-simulator/opendc-simulator-flow/src/main/kotlin/org/opendc/simulator/flow/internal/MutableFlowCounters.kt deleted file mode 100644 index c320a362..00000000 --- a/opendc-simulator/opendc-simulator-flow/src/main/kotlin/org/opendc/simulator/flow/internal/MutableFlowCounters.kt +++ /dev/null @@ -1,53 +0,0 @@ -/* - * 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.flow.internal - -import org.opendc.simulator.flow.FlowCounters - -/** - * Mutable implementation of the [FlowCounters] interface. - */ -public class MutableFlowCounters : FlowCounters { - override val demand: Double - get() = _counters[0] - override val actual: Double - get() = _counters[1] - override val remaining: Double - get() = _counters[2] - private val _counters = DoubleArray(3) - - override fun reset() { - _counters.fill(0.0) - } - - public fun increment(demand: Double, actual: Double, remaining: Double) { - val counters = _counters - counters[0] += demand - counters[1] += actual - counters[2] += remaining - } - - override fun toString(): String { - return "FlowCounters[demand=$demand,actual=$actual,remaining=$remaining]" - } -} diff --git a/opendc-simulator/opendc-simulator-flow/src/main/kotlin/org/opendc/simulator/flow/mux/FlowMultiplexer.kt b/opendc-simulator/opendc-simulator-flow/src/main/kotlin/org/opendc/simulator/flow/mux/FlowMultiplexer.kt deleted file mode 100644 index 8752c559..00000000 --- a/opendc-simulator/opendc-simulator-flow/src/main/kotlin/org/opendc/simulator/flow/mux/FlowMultiplexer.kt +++ /dev/null @@ -1,124 +0,0 @@ -/* - * 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.flow.mux - -import org.opendc.simulator.flow.FlowConsumer -import org.opendc.simulator.flow.FlowCounters -import org.opendc.simulator.flow.FlowSource - -/** - * A [FlowMultiplexer] enables multiplexing multiple [FlowSource]s over possibly multiple [FlowConsumer]s. - */ -public interface FlowMultiplexer { - /** - * The maximum number of inputs supported by the multiplexer. - */ - public val maxInputs: Int - - /** - * The maximum number of outputs supported by the multiplexer. - */ - public val maxOutputs: Int - - /** - * The inputs of the multiplexer that can be used to consume sources. - */ - public val inputs: Set<FlowConsumer> - - /** - * The outputs of the multiplexer over which the flows will be distributed. - */ - public val outputs: Set<FlowSource> - - /** - * The actual processing rate of the multiplexer. - */ - public val rate: Double - - /** - * The demanded processing rate of the input. - */ - public val demand: Double - - /** - * The capacity of the outputs. - */ - public val capacity: Double - - /** - * The flow counters to track the flow metrics of all multiplexer inputs. - */ - public val counters: FlowCounters - - /** - * Create a new input on this multiplexer with a coupled capacity. - */ - public fun newInput(): FlowConsumer - - /** - * Create a new input on this multiplexer with the specified [capacity]. - * - * @param capacity The capacity of the input. - */ - public fun newInput(capacity: Double): FlowConsumer - - /** - * Remove [input] from this multiplexer. - */ - public fun removeInput(input: FlowConsumer) - - /** - * Create a new output on this multiplexer. - */ - public fun newOutput(): FlowSource - - /** - * Remove [output] from this multiplexer. - */ - public fun removeOutput(output: FlowSource) - - /** - * Clear all inputs and outputs from the multiplexer. - */ - public fun clear() - - /** - * Clear the inputs of the multiplexer. - */ - public fun clearInputs() - - /** - * Clear the outputs of the multiplexer. - */ - public fun clearOutputs() - - /** - * Flush the counters of the multiplexer. - */ - public fun flushCounters() - - /** - * Flush the counters of the specified [input]. - */ - public fun flushCounters(input: FlowConsumer) -} diff --git a/opendc-simulator/opendc-simulator-flow/src/main/kotlin/org/opendc/simulator/flow/mux/FlowMultiplexerFactory.kt b/opendc-simulator/opendc-simulator-flow/src/main/kotlin/org/opendc/simulator/flow/mux/FlowMultiplexerFactory.kt deleted file mode 100644 index a863e3ad..00000000 --- a/opendc-simulator/opendc-simulator-flow/src/main/kotlin/org/opendc/simulator/flow/mux/FlowMultiplexerFactory.kt +++ /dev/null @@ -1,60 +0,0 @@ -/* - * Copyright (c) 2022 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.flow.mux - -import org.opendc.simulator.flow.FlowConvergenceListener -import org.opendc.simulator.flow.FlowEngine - -/** - * Factory interface for a [FlowMultiplexer] implementation. - */ -public fun interface FlowMultiplexerFactory { - /** - * Construct a new [FlowMultiplexer] using the specified [engine] and [listener]. - */ - public fun newMultiplexer(engine: FlowEngine, listener: FlowConvergenceListener?): FlowMultiplexer - - public companion object { - /** - * A [FlowMultiplexerFactory] constructing a [MaxMinFlowMultiplexer]. - */ - private val MAX_MIN_FACTORY = FlowMultiplexerFactory { engine, listener -> MaxMinFlowMultiplexer(engine, listener) } - - /** - * A [FlowMultiplexerFactory] constructing a [ForwardingFlowMultiplexer]. - */ - private val FORWARDING_FACTORY = FlowMultiplexerFactory { engine, listener -> ForwardingFlowMultiplexer(engine, listener) } - - /** - * Return a [FlowMultiplexerFactory] that returns [MaxMinFlowMultiplexer] instances. - */ - @JvmStatic - public fun maxMinMultiplexer(): FlowMultiplexerFactory = MAX_MIN_FACTORY - - /** - * Return a [ForwardingFlowMultiplexer] that returns [ForwardingFlowMultiplexer] instances. - */ - @JvmStatic - public fun forwardingMultiplexer(): FlowMultiplexerFactory = FORWARDING_FACTORY - } -} diff --git a/opendc-simulator/opendc-simulator-flow/src/main/kotlin/org/opendc/simulator/flow/mux/ForwardingFlowMultiplexer.kt b/opendc-simulator/opendc-simulator-flow/src/main/kotlin/org/opendc/simulator/flow/mux/ForwardingFlowMultiplexer.kt deleted file mode 100644 index 53f94a94..00000000 --- a/opendc-simulator/opendc-simulator-flow/src/main/kotlin/org/opendc/simulator/flow/mux/ForwardingFlowMultiplexer.kt +++ /dev/null @@ -1,177 +0,0 @@ -/* - * 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.flow.mux - -import org.opendc.simulator.flow.FlowConnection -import org.opendc.simulator.flow.FlowConsumer -import org.opendc.simulator.flow.FlowConvergenceListener -import org.opendc.simulator.flow.FlowCounters -import org.opendc.simulator.flow.FlowEngine -import org.opendc.simulator.flow.FlowForwarder -import org.opendc.simulator.flow.FlowSource -import java.util.ArrayDeque - -/** - * A [FlowMultiplexer] implementation that allocates inputs to the outputs of the multiplexer exclusively. This means - * that a single input is directly connected to an output and that the multiplexer can only support as many - * inputs as outputs. - * - * @param engine The [FlowEngine] driving the simulation. - * @param listener The convergence listener of the multiplexer. - */ -public class ForwardingFlowMultiplexer( - private val engine: FlowEngine, - private val listener: FlowConvergenceListener? = null -) : FlowMultiplexer, FlowConvergenceListener { - - override val maxInputs: Int - get() = _outputs.size - - override val maxOutputs: Int = Int.MAX_VALUE - - override val inputs: Set<FlowConsumer> - get() = _inputs - private val _inputs = mutableSetOf<Input>() - - override val outputs: Set<FlowSource> - get() = _outputs - private val _outputs = mutableSetOf<Output>() - private val _availableOutputs = ArrayDeque<Output>() - - override val counters: FlowCounters = object : FlowCounters { - override val demand: Double - get() = _outputs.sumOf { it.forwarder.counters.demand } - override val actual: Double - get() = _outputs.sumOf { it.forwarder.counters.actual } - override val remaining: Double - get() = _outputs.sumOf { it.forwarder.counters.remaining } - - override fun reset() { - for (output in _outputs) { - output.forwarder.counters.reset() - } - } - - override fun toString(): String = "FlowCounters[demand=$demand,actual=$actual,remaining=$remaining]" - } - - override val rate: Double - get() = _outputs.sumOf { it.forwarder.rate } - - override val demand: Double - get() = _outputs.sumOf { it.forwarder.demand } - - override val capacity: Double - get() = _outputs.sumOf { it.forwarder.capacity } - - override fun newInput(): FlowConsumer { - val output = checkNotNull(_availableOutputs.poll()) { "No capacity to serve request" } - val input = Input(output) - _inputs += input - return input - } - - override fun newInput(capacity: Double): FlowConsumer = newInput() - - override fun removeInput(input: FlowConsumer) { - if (!_inputs.remove(input)) { - return - } - - val output = (input as Input).output - output.forwarder.cancel() - _availableOutputs += output - } - - override fun newOutput(): FlowSource { - val forwarder = FlowForwarder(engine, this) - val output = Output(forwarder) - - _outputs += output - return output - } - - override fun removeOutput(output: FlowSource) { - if (!_outputs.remove(output)) { - return - } - - val forwarder = (output as Output).forwarder - forwarder.close() - } - - override fun clearInputs() { - for (input in _inputs) { - val output = input.output - output.forwarder.cancel() - _availableOutputs += output - } - - _inputs.clear() - } - - override fun clearOutputs() { - for (output in _outputs) { - output.forwarder.cancel() - } - _outputs.clear() - _availableOutputs.clear() - } - - override fun clear() { - clearOutputs() - clearInputs() - } - - override fun flushCounters() {} - - override fun flushCounters(input: FlowConsumer) {} - - override fun onConverge(now: Long) { - listener?.onConverge(now) - } - - /** - * An input on the multiplexer. - */ - private inner class Input(@JvmField val output: Output) : FlowConsumer by output.forwarder { - override fun toString(): String = "ForwardingFlowMultiplexer.Input" - } - - /** - * An output on the multiplexer. - */ - private inner class Output(@JvmField val forwarder: FlowForwarder) : FlowSource by forwarder { - override fun onStart(conn: FlowConnection, now: Long) { - _availableOutputs += this - forwarder.onStart(conn, now) - } - - override fun onStop(conn: FlowConnection, now: Long) { - forwarder.cancel() - forwarder.onStop(conn, now) - } - - override fun toString(): String = "ForwardingFlowMultiplexer.Output" - } -} diff --git a/opendc-simulator/opendc-simulator-flow/src/main/kotlin/org/opendc/simulator/flow/mux/MaxMinFlowMultiplexer.kt b/opendc-simulator/opendc-simulator-flow/src/main/kotlin/org/opendc/simulator/flow/mux/MaxMinFlowMultiplexer.kt deleted file mode 100644 index d9c6f893..00000000 --- a/opendc-simulator/opendc-simulator-flow/src/main/kotlin/org/opendc/simulator/flow/mux/MaxMinFlowMultiplexer.kt +++ /dev/null @@ -1,811 +0,0 @@ -/* - * 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.flow.mux - -import org.opendc.simulator.flow.FlowConnection -import org.opendc.simulator.flow.FlowConsumer -import org.opendc.simulator.flow.FlowConsumerContext -import org.opendc.simulator.flow.FlowConsumerLogic -import org.opendc.simulator.flow.FlowConvergenceListener -import org.opendc.simulator.flow.FlowCounters -import org.opendc.simulator.flow.FlowEngine -import org.opendc.simulator.flow.FlowSource -import org.opendc.simulator.flow.internal.D_MS_TO_S -import org.opendc.simulator.flow.internal.MutableFlowCounters -import kotlin.math.min - -/** - * A [FlowMultiplexer] implementation that multiplexes flows over the available outputs using max-min fair sharing. - * - * @param engine The [FlowEngine] to drive the flow simulation. - * @param parent The parent flow system of the multiplexer. - */ -public class MaxMinFlowMultiplexer( - private val engine: FlowEngine, - parent: FlowConvergenceListener? = null -) : FlowMultiplexer { - - override val maxInputs: Int = Int.MAX_VALUE - - override val maxOutputs: Int = Int.MAX_VALUE - - /** - * The inputs of the multiplexer. - */ - override val inputs: Set<FlowConsumer> - get() = _inputs - private val _inputs = mutableSetOf<Input>() - - /** - * The outputs of the multiplexer. - */ - override val outputs: Set<FlowSource> - get() = _outputs - private val _outputs = mutableSetOf<Output>() - - /** - * The flow counters of this multiplexer. - */ - public override val counters: FlowCounters - get() = scheduler.counters - - /** - * The actual processing rate of the multiplexer. - */ - public override val rate: Double - get() = scheduler.rate - - /** - * The demanded processing rate of the input. - */ - public override val demand: Double - get() = scheduler.demand - - /** - * The capacity of the outputs. - */ - public override val capacity: Double - get() = scheduler.capacity - - /** - * The [Scheduler] instance of this multiplexer. - */ - private val scheduler = Scheduler(engine, parent) - - override fun newInput(): FlowConsumer { - return newInput(isCoupled = true, Double.POSITIVE_INFINITY) - } - - override fun newInput(capacity: Double): FlowConsumer { - return newInput(isCoupled = false, capacity) - } - - private fun newInput(isCoupled: Boolean, initialCapacity: Double): FlowConsumer { - val provider = Input(engine, scheduler, isCoupled, initialCapacity) - _inputs.add(provider) - return provider - } - - override fun removeInput(input: FlowConsumer) { - if (!_inputs.remove(input)) { - return - } - // This cast should always succeed since only `Input` instances should be added to `_inputs` - (input as Input).close() - } - - override fun newOutput(): FlowSource { - val output = Output(scheduler) - _outputs.add(output) - return output - } - - override fun removeOutput(output: FlowSource) { - if (!_outputs.remove(output)) { - return - } - - // This cast should always succeed since only `Output` instances should be added to `_outputs` - (output as Output).cancel() - } - - override fun clearInputs() { - for (input in _inputs) { - input.cancel() - } - _inputs.clear() - } - - override fun clearOutputs() { - for (output in _outputs) { - output.cancel() - } - _outputs.clear() - } - - override fun clear() { - clearOutputs() - clearInputs() - } - - override fun flushCounters() { - scheduler.updateCounters(engine.clock.millis()) - } - - override fun flushCounters(input: FlowConsumer) { - (input as Input).doUpdateCounters(engine.clock.millis()) - } - - /** - * Helper class containing the scheduler state. - */ - private class Scheduler(engine: FlowEngine, private val parent: FlowConvergenceListener?) { - /** - * The flow counters of this scheduler. - */ - @JvmField val counters = MutableFlowCounters() - - /** - * The flow rate of the multiplexer. - */ - @JvmField var rate = 0.0 - - /** - * The demand for the multiplexer. - */ - @JvmField var demand = 0.0 - - /** - * The capacity of the multiplexer. - */ - @JvmField var capacity = 0.0 - - /** - * An [Output] that is used to activate the scheduler. - */ - @JvmField var activationOutput: Output? = null - - /** - * The active inputs registered with the scheduler. - */ - private val _activeInputs = mutableListOf<Input>() - - /** - * An array containing the active inputs, which is used to reduce the overhead of an [ArrayList]. - */ - private var _inputArray = emptyArray<Input>() - - /** - * The active outputs registered with the scheduler. - */ - private val _activeOutputs = mutableListOf<Output>() - - /** - * Flag to indicate that the scheduler is active. - */ - private var _schedulerActive = false - - /** - * The last convergence timestamp and the input. - */ - private var _lastConverge: Long = Long.MIN_VALUE - private var _lastConvergeInput: Input? = null - - /** - * The simulation clock. - */ - private val _clock = engine.clock - - /** - * Register the specified [input] to this scheduler. - */ - fun registerInput(input: Input) { - _activeInputs.add(input) - _inputArray = _activeInputs.toTypedArray() - - val hasActivationOutput = activationOutput != null - - // Disable timers and convergence of the source if one of the output manages it - input.shouldConsumerConverge = !hasActivationOutput - input.enableTimers = !hasActivationOutput - - if (input.isCoupled) { - input.capacity = capacity - } - - trigger(_clock.millis()) - } - - /** - * De-register the specified [input] from this scheduler. - */ - fun deregisterInput(input: Input, now: Long) { - // Assign a new input responsible for handling the convergence events - if (_lastConvergeInput == input) { - _lastConvergeInput = null - } - - _activeInputs.remove(input) - - // Re-run scheduler to distribute new load - trigger(now) - } - - /** - * This method is invoked when one of the inputs converges. - */ - fun convergeInput(input: Input, now: Long) { - val lastConverge = _lastConverge - val lastConvergeInput = _lastConvergeInput - val parent = parent - - if (parent != null && (now > lastConverge || lastConvergeInput == null || lastConvergeInput == input)) { - _lastConverge = now - _lastConvergeInput = input - - parent.onConverge(now) - } - } - - /** - * Register the specified [output] to this scheduler. - */ - fun registerOutput(output: Output) { - _activeOutputs.add(output) - - updateCapacity() - updateActivationOutput() - } - - /** - * De-register the specified [output] from this scheduler. - */ - fun deregisterOutput(output: Output, now: Long) { - _activeOutputs.remove(output) - updateCapacity() - - trigger(now) - } - - /** - * This method is invoked when one of the outputs converges. - */ - fun convergeOutput(output: Output, now: Long) { - val parent = parent - - if (parent != null) { - _lastConverge = now - parent.onConverge(now) - } - - if (!output.isActive) { - output.isActivationOutput = false - updateActivationOutput() - } - } - - /** - * Trigger the scheduler of the multiplexer. - * - * @param now The current virtual timestamp of the simulation. - */ - fun trigger(now: Long) { - if (_schedulerActive) { - // No need to trigger the scheduler in case it is already active - return - } - - val activationOutput = activationOutput - - // We can run the scheduler in two ways: - // (1) We can pull one of the multiplexer's outputs. This allows us to cascade multiple pushes by the input - // into a single scheduling cycle, but is slower in case of a few changes at the same timestamp. - // (2) We run the scheduler directly from this method call. This is the fastest approach when there are only - // a few inputs and little changes at the same timestamp. - // We always pick for option (1) unless there are no outputs available. - if (activationOutput != null) { - activationOutput.pull(now) - return - } else { - runScheduler(now) - } - } - - /** - * Synchronously run the scheduler of the multiplexer. - */ - fun runScheduler(now: Long): Long { - return try { - _schedulerActive = true - doRunScheduler(now) - } finally { - _schedulerActive = false - } - } - - /** - * Recompute the capacity of the multiplexer. - */ - fun updateCapacity() { - val newCapacity = _activeOutputs.sumOf(Output::capacity) - - // No-op if the capacity is unchanged - if (capacity == newCapacity) { - return - } - - capacity = newCapacity - - for (input in _activeInputs) { - if (input.isCoupled) { - input.capacity = newCapacity - } - } - - // Sort outputs by their capacity - _activeOutputs.sort() - } - - /** - * Updates the output that is used for scheduler activation. - */ - private fun updateActivationOutput() { - val output = _activeOutputs.firstOrNull() - activationOutput = output - - if (output != null) { - output.isActivationOutput = true - } - - val hasActivationOutput = output != null - - for (input in _activeInputs) { - input.shouldConsumerConverge = !hasActivationOutput - input.enableTimers = !hasActivationOutput - } - } - - /** - * Schedule the inputs over the outputs. - * - * @return The deadline after which a new scheduling cycle should start. - */ - private fun doRunScheduler(now: Long): Long { - val activeInputs = _activeInputs - val activeOutputs = _activeOutputs - var inputArray = _inputArray - var inputSize = _inputArray.size - - // Update the counters of the scheduler - updateCounters(now) - - // If there is no work yet, mark the inputs as idle. - if (inputSize == 0) { - demand = 0.0 - rate = 0.0 - return Long.MAX_VALUE - } - - val capacity = capacity - var availableCapacity = capacity - var deadline = Long.MAX_VALUE - var demand = 0.0 - var shouldRebuild = false - - // Pull in the work of the inputs - for (i in 0 until inputSize) { - val input = inputArray[i] - - input.pullSync(now) - - // Remove inputs that have finished - if (!input.isActive) { - input.actualRate = 0.0 - shouldRebuild = true - } else { - demand += input.limit - deadline = min(deadline, input.deadline) - } - } - - // Slow-path: Rebuild the input array based on the (apparently) updated `activeInputs` - if (shouldRebuild) { - inputArray = activeInputs.toTypedArray() - inputSize = inputArray.size - _inputArray = inputArray - } - - val rate = if (demand > capacity) { - // If the demand is higher than the capacity, we need use max-min fair sharing to distribute the - // constrained capacity across the inputs. - - // Sort in-place the inputs based on their pushed flow. - // Profiling shows that it is faster than maintaining some kind of sorted set. - inputArray.sort() - - // Divide the available output capacity fairly over the inputs using max-min fair sharing - for (i in 0 until inputSize) { - val input = inputArray[i] - val availableShare = availableCapacity / (inputSize - i) - val grantedRate = min(input.allowedRate, availableShare) - - availableCapacity -= grantedRate - input.actualRate = grantedRate - } - - capacity - availableCapacity - } else { - demand - } - - this.demand = demand - if (this.rate != rate) { - // Only update the outputs if the output rate has changed - this.rate = rate - - // Divide the requests over the available capacity of the input resources fairly - for (i in activeOutputs.indices) { - val output = activeOutputs[i] - val inputCapacity = output.capacity - val fraction = inputCapacity / capacity - val grantedSpeed = rate * fraction - - output.push(grantedSpeed) - } - } - - return deadline - } - - /** - * The previous capacity of the multiplexer. - */ - private var _previousCapacity = 0.0 - private var _previousUpdate = Long.MIN_VALUE - - /** - * Update the counters of the scheduler. - */ - fun updateCounters(now: Long) { - val previousCapacity = _previousCapacity - _previousCapacity = capacity - - val previousUpdate = _previousUpdate - _previousUpdate = now - - val delta = now - previousUpdate - if (delta <= 0) { - return - } - - val deltaS = delta * D_MS_TO_S - val demand = demand - val rate = rate - - counters.increment( - demand = demand * deltaS, - actual = rate * deltaS, - remaining = (previousCapacity - rate) * deltaS - ) - } - } - - /** - * An internal [FlowConsumer] implementation for multiplexer inputs. - */ - private class Input( - private val engine: FlowEngine, - private val scheduler: Scheduler, - @JvmField val isCoupled: Boolean, - initialCapacity: Double - ) : FlowConsumer, FlowConsumerLogic, Comparable<Input> { - /** - * A flag to indicate that the consumer is active. - */ - override val isActive: Boolean - get() = _ctx != null - - /** - * The demand of the consumer. - */ - override val demand: Double - get() = limit - - /** - * The processing rate of the consumer. - */ - override val rate: Double - get() = actualRate - - /** - * The capacity of the input. - */ - override var capacity: Double - get() = _capacity - set(value) { - allowedRate = min(limit, value) - _capacity = value - _ctx?.capacity = value - } - private var _capacity = initialCapacity - - /** - * The flow counters to track the flow metrics of the consumer. - */ - override val counters: FlowCounters - get() = _counters - private val _counters = MutableFlowCounters() - - /** - * A flag to enable timers for the input. - */ - var enableTimers: Boolean = true - set(value) { - field = value - _ctx?.enableTimers = value - } - - /** - * A flag to control whether the input should converge. - */ - var shouldConsumerConverge: Boolean = true - set(value) { - field = value - _ctx?.shouldConsumerConverge = value - } - - /** - * The requested limit. - */ - @JvmField var limit: Double = 0.0 - - /** - * The actual processing speed. - */ - @JvmField var actualRate: Double = 0.0 - - /** - * The processing rate that is allowed by the model constraints. - */ - @JvmField var allowedRate: Double = 0.0 - - /** - * The deadline of the input. - */ - val deadline: Long - get() = _ctx?.deadline ?: Long.MAX_VALUE - - /** - * The [FlowConsumerContext] that is currently running. - */ - private var _ctx: FlowConsumerContext? = null - - /** - * A flag to indicate that the input is closed. - */ - private var _isClosed: Boolean = false - - /** - * Close the input. - * - * This method is invoked when the user removes an input from the switch. - */ - fun close() { - _isClosed = true - cancel() - } - - /** - * Pull the source if necessary. - */ - fun pullSync(now: Long) { - _ctx?.pullSync(now) - } - - /* FlowConsumer */ - override fun startConsumer(source: FlowSource) { - check(!_isClosed) { "Cannot re-use closed input" } - check(_ctx == null) { "Consumer is in invalid state" } - - val ctx = engine.newContext(source, this) - _ctx = ctx - - ctx.capacity = capacity - scheduler.registerInput(this) - - ctx.start() - } - - override fun pull() { - _ctx?.pull() - } - - override fun cancel() { - _ctx?.close() - } - - /* FlowConsumerLogic */ - override fun onPush( - ctx: FlowConsumerContext, - now: Long, - rate: Double - ) { - doUpdateCounters(now) - - val allowed = min(rate, capacity) - limit = rate - actualRate = allowed - allowedRate = allowed - - scheduler.trigger(now) - } - - override fun onFinish(ctx: FlowConsumerContext, now: Long, cause: Throwable?) { - doUpdateCounters(now) - - limit = 0.0 - actualRate = 0.0 - allowedRate = 0.0 - - scheduler.deregisterInput(this, now) - - _ctx = null - } - - override fun onConverge(ctx: FlowConsumerContext, now: Long) { - scheduler.convergeInput(this, now) - } - - /* Comparable */ - override fun compareTo(other: Input): Int = allowedRate.compareTo(other.allowedRate) - - /** - * The timestamp that the counters where last updated. - */ - private var _lastUpdate = Long.MIN_VALUE - - /** - * Helper method to update the flow counters of the multiplexer. - */ - fun doUpdateCounters(now: Long) { - val lastUpdate = _lastUpdate - _lastUpdate = now - - val delta = (now - lastUpdate).coerceAtLeast(0) - if (delta <= 0L) { - return - } - - val actualRate = actualRate - - val deltaS = delta * D_MS_TO_S - val demand = limit * deltaS - val actual = actualRate * deltaS - val remaining = (_capacity - actualRate) * deltaS - - _counters.increment(demand, actual, remaining) - scheduler.counters.increment(0.0, 0.0, 0.0) - } - } - - /** - * An internal [FlowSource] implementation for multiplexer outputs. - */ - private class Output(private val scheduler: Scheduler) : FlowSource, Comparable<Output> { - /** - * The active [FlowConnection] of this source. - */ - private var _conn: FlowConnection? = null - - /** - * The capacity of this output. - */ - @JvmField var capacity: Double = 0.0 - - /** - * A flag to indicate that this output is the activation output. - */ - var isActivationOutput: Boolean - get() = _isActivationOutput - set(value) { - _isActivationOutput = value - _conn?.shouldSourceConverge = value - } - private var _isActivationOutput: Boolean = false - - /** - * A flag to indicate that the output is active. - */ - @JvmField var isActive = false - - /** - * Push the specified rate to the consumer. - */ - fun push(rate: Double) { - _conn?.push(rate) - } - - /** - * Cancel this output. - */ - fun cancel() { - _conn?.close() - } - - /** - * Pull this output. - */ - fun pull(now: Long) { - _conn?.pull(now) - } - - override fun onStart(conn: FlowConnection, now: Long) { - assert(_conn == null) { "Source running concurrently" } - _conn = conn - capacity = conn.capacity - isActive = true - - scheduler.registerOutput(this) - } - - override fun onStop(conn: FlowConnection, now: Long) { - _conn = null - capacity = 0.0 - isActive = false - - scheduler.deregisterOutput(this, now) - } - - override fun onPull(conn: FlowConnection, now: Long): Long { - val capacity = capacity - if (capacity != conn.capacity) { - this.capacity = capacity - scheduler.updateCapacity() - } - - return if (_isActivationOutput) { - // If this output is the activation output, synchronously run the scheduler and return the new deadline - val deadline = scheduler.runScheduler(now) - if (deadline == Long.MAX_VALUE) { - deadline - } else { - deadline - now - } - } else { - // Output is not the activation output, so trigger activation output and do not install timer for this - // output (by returning `Long.MAX_VALUE`) - scheduler.trigger(now) - - Long.MAX_VALUE - } - } - - override fun onConverge(conn: FlowConnection, now: Long) { - if (_isActivationOutput) { - scheduler.convergeOutput(this, now) - } - } - - override fun compareTo(other: Output): Int = capacity.compareTo(other.capacity) - } -} diff --git a/opendc-simulator/opendc-simulator-flow/src/main/kotlin/org/opendc/simulator/flow/source/FixedFlowSource.kt b/opendc-simulator/opendc-simulator-flow/src/main/kotlin/org/opendc/simulator/flow/source/FixedFlowSource.kt deleted file mode 100644 index 6cfcc82c..00000000 --- a/opendc-simulator/opendc-simulator-flow/src/main/kotlin/org/opendc/simulator/flow/source/FixedFlowSource.kt +++ /dev/null @@ -1,66 +0,0 @@ -/* - * 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.flow.source - -import org.opendc.simulator.flow.FlowConnection -import org.opendc.simulator.flow.FlowSource -import kotlin.math.roundToLong - -/** - * A [FlowSource] that contains a fixed [amount] and is pushed with a given [utilization]. - */ -public class FixedFlowSource(private val amount: Double, private val utilization: Double) : FlowSource { - - init { - require(amount >= 0.0) { "Amount must be positive" } - require(utilization > 0.0) { "Utilization must be positive" } - } - - private var remainingAmount = amount - private var lastPull: Long = 0L - - override fun onStart(conn: FlowConnection, now: Long) { - lastPull = now - } - - override fun onPull(conn: FlowConnection, now: Long): Long { - val lastPull = lastPull - this.lastPull = now - val delta = (now - lastPull).coerceAtLeast(0) - - val consumed = conn.rate * delta / 1000.0 - val limit = conn.capacity * utilization - - remainingAmount -= consumed - - val duration = (remainingAmount / limit * 1000).roundToLong() - - return if (duration > 0) { - conn.push(limit) - duration - } else { - conn.close() - Long.MAX_VALUE - } - } -} diff --git a/opendc-simulator/opendc-simulator-flow/src/main/kotlin/org/opendc/simulator/flow/source/FlowSourceRateAdapter.kt b/opendc-simulator/opendc-simulator-flow/src/main/kotlin/org/opendc/simulator/flow/source/FlowSourceRateAdapter.kt deleted file mode 100644 index 80127fb5..00000000 --- a/opendc-simulator/opendc-simulator-flow/src/main/kotlin/org/opendc/simulator/flow/source/FlowSourceRateAdapter.kt +++ /dev/null @@ -1,77 +0,0 @@ -/* - * 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.flow.source - -import org.opendc.simulator.flow.FlowConnection -import org.opendc.simulator.flow.FlowSource - -/** - * Helper class to expose an observable [rate] field describing the flow rate of the source. - */ -public class FlowSourceRateAdapter( - private val delegate: FlowSource, - private val callback: (Double) -> Unit = {} -) : FlowSource by delegate { - /** - * The resource processing speed at this instant. - */ - public var rate: Double = 0.0 - private set(value) { - if (field != value) { - callback(value) - field = value - } - } - - init { - callback(0.0) - } - - override fun onStart(conn: FlowConnection, now: Long) { - conn.shouldSourceConverge = true - - delegate.onStart(conn, now) - } - - override fun onStop(conn: FlowConnection, now: Long) { - try { - delegate.onStop(conn, now) - } finally { - rate = 0.0 - } - } - - override fun onPull(conn: FlowConnection, now: Long): Long { - return delegate.onPull(conn, now) - } - - override fun onConverge(conn: FlowConnection, now: Long) { - try { - delegate.onConverge(conn, now) - } finally { - rate = conn.rate - } - } - - override fun toString(): String = "FlowSourceRateAdapter[delegate=$delegate]" -} diff --git a/opendc-simulator/opendc-simulator-flow/src/main/kotlin/org/opendc/simulator/flow/source/TraceFlowSource.kt b/opendc-simulator/opendc-simulator-flow/src/main/kotlin/org/opendc/simulator/flow/source/TraceFlowSource.kt deleted file mode 100644 index c9a52128..00000000 --- a/opendc-simulator/opendc-simulator-flow/src/main/kotlin/org/opendc/simulator/flow/source/TraceFlowSource.kt +++ /dev/null @@ -1,67 +0,0 @@ -/* - * 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.flow.source - -import org.opendc.simulator.flow.FlowConnection -import org.opendc.simulator.flow.FlowSource - -/** - * A [FlowSource] that replays a sequence of [Fragment], each indicating the flow rate for some period of time. - */ -public class TraceFlowSource(private val trace: Sequence<Fragment>) : FlowSource { - private var _iterator: Iterator<Fragment>? = null - private var _nextTarget = Long.MIN_VALUE - - override fun onStart(conn: FlowConnection, now: Long) { - check(_iterator == null) { "Source already running" } - _iterator = trace.iterator() - } - - override fun onStop(conn: FlowConnection, now: Long) { - _iterator = null - } - - override fun onPull(conn: FlowConnection, now: Long): Long { - // Check whether the trace fragment was fully consumed, otherwise wait until we have done so - val nextTarget = _nextTarget - if (nextTarget > now) { - return now - nextTarget - } - - val iterator = checkNotNull(_iterator) - return if (iterator.hasNext()) { - val fragment = iterator.next() - _nextTarget = now + fragment.duration - conn.push(fragment.usage) - fragment.duration - } else { - conn.close() - Long.MAX_VALUE - } - } - - /** - * A fragment of the trace. - */ - public data class Fragment(val duration: Long, val usage: Double) -} diff --git a/opendc-simulator/opendc-simulator-flow/src/test/kotlin/org/opendc/simulator/flow/FlowConsumerContextTest.kt b/opendc-simulator/opendc-simulator-flow/src/test/kotlin/org/opendc/simulator/flow/FlowConsumerContextTest.kt deleted file mode 100644 index f89133dd..00000000 --- a/opendc-simulator/opendc-simulator-flow/src/test/kotlin/org/opendc/simulator/flow/FlowConsumerContextTest.kt +++ /dev/null @@ -1,107 +0,0 @@ -/* - * 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.flow - -import io.mockk.spyk -import io.mockk.verify -import net.bytebuddy.matcher.ElementMatchers.any -import org.junit.jupiter.api.Test -import org.junit.jupiter.api.assertThrows -import org.opendc.simulator.flow.internal.FlowConsumerContextImpl -import org.opendc.simulator.flow.internal.FlowEngineImpl -import org.opendc.simulator.kotlin.runSimulation - -/** - * A test suite for the [FlowConsumerContextImpl] class. - */ -class FlowConsumerContextTest { - @Test - fun testFlushWithoutCommand() = runSimulation { - val engine = FlowEngineImpl(coroutineContext, clock) - val consumer = object : FlowSource { - override fun onPull(conn: FlowConnection, now: Long): Long { - return if (now == 0L) { - conn.push(1.0) - 1000 - } else { - conn.close() - Long.MAX_VALUE - } - } - } - - val logic = object : FlowConsumerLogic {} - val context = FlowConsumerContextImpl(engine, consumer, logic) - - engine.scheduleSync(engine.clock.millis(), context) - } - - @Test - fun testDoubleStart() = runSimulation { - val engine = FlowEngineImpl(coroutineContext, clock) - val consumer = object : FlowSource { - override fun onPull(conn: FlowConnection, now: Long): Long { - return if (now == 0L) { - conn.push(0.0) - 1000 - } else { - conn.close() - Long.MAX_VALUE - } - } - } - - val logic = object : FlowConsumerLogic {} - val context = FlowConsumerContextImpl(engine, consumer, logic) - - context.start() - - assertThrows<IllegalStateException> { - context.start() - } - } - - @Test - fun testIdempotentCapacityChange() = runSimulation { - val engine = FlowEngineImpl(coroutineContext, clock) - val consumer = spyk(object : FlowSource { - override fun onPull(conn: FlowConnection, now: Long): Long { - return if (now == 0L) { - conn.push(1.0) - 1000 - } else { - conn.close() - Long.MAX_VALUE - } - } - }) - - val logic = object : FlowConsumerLogic {} - val context = FlowConsumerContextImpl(engine, consumer, logic) - context.capacity = 4200.0 - context.start() - context.capacity = 4200.0 - - verify(exactly = 1) { consumer.onPull(any(), any()) } - } -} diff --git a/opendc-simulator/opendc-simulator-flow/src/test/kotlin/org/opendc/simulator/flow/FlowForwarderTest.kt b/opendc-simulator/opendc-simulator-flow/src/test/kotlin/org/opendc/simulator/flow/FlowForwarderTest.kt deleted file mode 100644 index f75e5037..00000000 --- a/opendc-simulator/opendc-simulator-flow/src/test/kotlin/org/opendc/simulator/flow/FlowForwarderTest.kt +++ /dev/null @@ -1,331 +0,0 @@ -/* - * 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.flow - -import io.mockk.spyk -import io.mockk.verify -import kotlinx.coroutines.coroutineScope -import kotlinx.coroutines.delay -import kotlinx.coroutines.launch -import kotlinx.coroutines.yield -import net.bytebuddy.matcher.ElementMatchers.any -import org.junit.jupiter.api.Assertions.assertAll -import org.junit.jupiter.api.Assertions.assertEquals -import org.junit.jupiter.api.Assertions.assertFalse -import org.junit.jupiter.api.Assertions.assertTrue -import org.junit.jupiter.api.Disabled -import org.junit.jupiter.api.Test -import org.junit.jupiter.api.assertThrows -import org.opendc.simulator.flow.internal.FlowEngineImpl -import org.opendc.simulator.flow.source.FixedFlowSource -import org.opendc.simulator.kotlin.runSimulation - -/** - * A test suite for the [FlowForwarder] class. - */ -internal class FlowForwarderTest { - @Test - fun testCancelImmediately() = runSimulation { - val engine = FlowEngineImpl(coroutineContext, clock) - val forwarder = FlowForwarder(engine) - val source = FlowSink(engine, 2000.0) - - launch { source.consume(forwarder) } - - forwarder.consume(object : FlowSource { - override fun onPull(conn: FlowConnection, now: Long): Long { - conn.close() - return Long.MAX_VALUE - } - }) - - forwarder.close() - source.cancel() - } - - @Test - fun testCancel() = runSimulation { - val engine = FlowEngineImpl(coroutineContext, clock) - val forwarder = FlowForwarder(engine) - val source = FlowSink(engine, 2000.0) - - launch { source.consume(forwarder) } - - forwarder.consume(object : FlowSource { - var isFirst = true - - override fun onPull(conn: FlowConnection, now: Long): Long { - return if (isFirst) { - isFirst = false - conn.push(1.0) - 10 * 1000 - } else { - conn.close() - Long.MAX_VALUE - } - } - }) - - forwarder.close() - source.cancel() - } - - @Test - fun testState() = runSimulation { - val engine = FlowEngineImpl(coroutineContext, clock) - val forwarder = FlowForwarder(engine) - val consumer = object : FlowSource { - override fun onPull(conn: FlowConnection, now: Long): Long { - conn.close() - return Long.MAX_VALUE - } - } - - assertFalse(forwarder.isActive) - - forwarder.startConsumer(consumer) - assertTrue(forwarder.isActive) - - assertThrows<IllegalStateException> { forwarder.startConsumer(consumer) } - - forwarder.cancel() - assertFalse(forwarder.isActive) - - forwarder.close() - assertFalse(forwarder.isActive) - } - - @Test - fun testCancelPendingDelegate() = runSimulation { - val engine = FlowEngineImpl(coroutineContext, clock) - val forwarder = FlowForwarder(engine) - - val consumer = spyk(object : FlowSource { - override fun onPull(conn: FlowConnection, now: Long): Long { - conn.close() - return Long.MAX_VALUE - } - }) - - forwarder.startConsumer(consumer) - forwarder.cancel() - - verify(exactly = 0) { consumer.onStop(any(), any()) } - } - - @Test - fun testCancelStartedDelegate() = runSimulation { - val engine = FlowEngineImpl(coroutineContext, clock) - val forwarder = FlowForwarder(engine) - val source = FlowSink(engine, 2000.0) - - val consumer = spyk(FixedFlowSource(2000.0, 1.0)) - - source.startConsumer(forwarder) - yield() - forwarder.startConsumer(consumer) - yield() - forwarder.cancel() - - verify(exactly = 1) { consumer.onStart(any(), any()) } - verify(exactly = 1) { consumer.onStop(any(), any()) } - } - - @Test - fun testCancelPropagation() = runSimulation { - val engine = FlowEngineImpl(coroutineContext, clock) - val forwarder = FlowForwarder(engine) - val source = FlowSink(engine, 2000.0) - - val consumer = spyk(FixedFlowSource(2000.0, 1.0)) - - source.startConsumer(forwarder) - yield() - forwarder.startConsumer(consumer) - yield() - source.cancel() - - verify(exactly = 1) { consumer.onStart(any(), any()) } - verify(exactly = 1) { consumer.onStop(any(), any()) } - } - - @Test - fun testExitPropagation() = runSimulation { - val engine = FlowEngineImpl(coroutineContext, clock) - val forwarder = FlowForwarder(engine, isCoupled = true) - val source = FlowSink(engine, 2000.0) - - val consumer = object : FlowSource { - override fun onPull(conn: FlowConnection, now: Long): Long { - conn.close() - return Long.MAX_VALUE - } - } - - source.startConsumer(forwarder) - forwarder.consume(consumer) - yield() - - assertFalse(forwarder.isActive) - } - - @Test - @Disabled // Due to Kotlin bug: https://github.com/mockk/mockk/issues/368 - fun testAdjustCapacity() = runSimulation { - val engine = FlowEngineImpl(coroutineContext, clock) - val forwarder = FlowForwarder(engine) - val sink = FlowSink(engine, 1.0) - - val source = spyk(FixedFlowSource(2.0, 1.0)) - sink.startConsumer(forwarder) - - coroutineScope { - launch { forwarder.consume(source) } - delay(1000) - sink.capacity = 0.5 - } - - assertEquals(3000, clock.millis()) - verify(exactly = 1) { source.onPull(any(), any()) } - } - - @Test - fun testCounters() = runSimulation { - val engine = FlowEngineImpl(coroutineContext, clock) - val forwarder = FlowForwarder(engine) - val source = FlowSink(engine, 1.0) - - val consumer = FixedFlowSource(2.0, 1.0) - source.startConsumer(forwarder) - - forwarder.consume(consumer) - - yield() - - assertAll( - { assertEquals(2.0, source.counters.actual) }, - { assertEquals(source.counters.actual, forwarder.counters.actual) { "Actual work" } }, - { assertEquals(source.counters.demand, forwarder.counters.demand) { "Work demand" } }, - { assertEquals(source.counters.remaining, forwarder.counters.remaining) { "Overcommitted work" } }, - { assertEquals(2000, clock.millis()) } - ) - } - - @Test - fun testCoupledExit() = runSimulation { - val engine = FlowEngineImpl(coroutineContext, clock) - val forwarder = FlowForwarder(engine, isCoupled = true) - val source = FlowSink(engine, 2000.0) - - launch { source.consume(forwarder) } - - forwarder.consume(FixedFlowSource(2000.0, 1.0)) - - yield() - - assertFalse(source.isActive) - } - - @Test - fun testPullFailureCoupled() = runSimulation { - val engine = FlowEngineImpl(coroutineContext, clock) - val forwarder = FlowForwarder(engine, isCoupled = true) - val source = FlowSink(engine, 2000.0) - - launch { source.consume(forwarder) } - - try { - forwarder.consume(object : FlowSource { - override fun onPull(conn: FlowConnection, now: Long): Long { - throw IllegalStateException("Test") - } - }) - } catch (cause: Throwable) { - // Ignore - } - - yield() - - assertFalse(source.isActive) - } - - @Test - fun testStartFailure() = runSimulation { - val engine = FlowEngineImpl(coroutineContext, clock) - val forwarder = FlowForwarder(engine) - val source = FlowSink(engine, 2000.0) - - launch { source.consume(forwarder) } - - try { - forwarder.consume(object : FlowSource { - override fun onPull(conn: FlowConnection, now: Long): Long { - return Long.MAX_VALUE - } - - override fun onStart(conn: FlowConnection, now: Long) { - throw IllegalStateException("Test") - } - }) - } catch (cause: Throwable) { - // Ignore - } - - yield() - - assertTrue(source.isActive) - source.cancel() - } - - @Test - fun testConvergeFailure() = runSimulation { - val engine = FlowEngineImpl(coroutineContext, clock) - val forwarder = FlowForwarder(engine) - val source = FlowSink(engine, 2000.0) - - launch { source.consume(forwarder) } - - try { - forwarder.consume(object : FlowSource { - override fun onStart(conn: FlowConnection, now: Long) { - conn.shouldSourceConverge = true - } - - override fun onPull(conn: FlowConnection, now: Long): Long { - return Long.MAX_VALUE - } - - override fun onConverge(conn: FlowConnection, now: Long) { - throw IllegalStateException("Test") - } - }) - } catch (cause: Throwable) { - // Ignore - } - - yield() - - assertTrue(source.isActive) - source.cancel() - } -} diff --git a/opendc-simulator/opendc-simulator-flow/src/test/kotlin/org/opendc/simulator/flow/FlowSinkTest.kt b/opendc-simulator/opendc-simulator-flow/src/test/kotlin/org/opendc/simulator/flow/FlowSinkTest.kt deleted file mode 100644 index 746d752d..00000000 --- a/opendc-simulator/opendc-simulator-flow/src/test/kotlin/org/opendc/simulator/flow/FlowSinkTest.kt +++ /dev/null @@ -1,245 +0,0 @@ -/* - * 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.flow - -import io.mockk.spyk -import io.mockk.verify -import kotlinx.coroutines.coroutineScope -import kotlinx.coroutines.delay -import kotlinx.coroutines.launch -import kotlinx.coroutines.yield -import org.junit.jupiter.api.Assertions.assertEquals -import org.junit.jupiter.api.Test -import org.junit.jupiter.api.assertThrows -import org.opendc.simulator.flow.internal.FlowEngineImpl -import org.opendc.simulator.flow.source.FixedFlowSource -import org.opendc.simulator.flow.source.FlowSourceRateAdapter -import org.opendc.simulator.kotlin.runSimulation - -/** - * A test suite for the [FlowSink] class. - */ -internal class FlowSinkTest { - @Test - fun testSpeed() = runSimulation { - val engine = FlowEngineImpl(coroutineContext, clock) - val capacity = 4200.0 - val provider = FlowSink(engine, capacity) - - val consumer = FixedFlowSource(4200.0, 1.0) - - val res = mutableListOf<Double>() - val adapter = FlowSourceRateAdapter(consumer, res::add) - - provider.consume(adapter) - - assertEquals(listOf(0.0, capacity, 0.0), res) { "Speed is reported correctly" } - } - - @Test - fun testAdjustCapacity() = runSimulation { - val engine = FlowEngineImpl(coroutineContext, clock) - val provider = FlowSink(engine, 1.0) - - val consumer = spyk(FixedFlowSource(2.0, 1.0)) - - coroutineScope { - launch { provider.consume(consumer) } - delay(1000) - provider.capacity = 0.5 - } - assertEquals(3000, clock.millis()) - verify(exactly = 3) { consumer.onPull(any(), any()) } - } - - @Test - fun testSpeedLimit() = runSimulation { - val engine = FlowEngineImpl(coroutineContext, clock) - val capacity = 4200.0 - val provider = FlowSink(engine, capacity) - - val consumer = FixedFlowSource(capacity, 2.0) - - val res = mutableListOf<Double>() - val adapter = FlowSourceRateAdapter(consumer, res::add) - - provider.consume(adapter) - - assertEquals(listOf(0.0, capacity, 0.0), res) { "Speed is reported correctly" } - } - - /** - * Test to see whether no infinite recursion occurs when interrupting during [FlowSource.onStart] or - * [FlowSource.onPull]. - */ - @Test - fun testIntermediateInterrupt() = runSimulation { - val engine = FlowEngineImpl(coroutineContext, clock) - val capacity = 4200.0 - val provider = FlowSink(engine, capacity) - - val consumer = object : FlowSource { - override fun onPull(conn: FlowConnection, now: Long): Long { - conn.close() - return Long.MAX_VALUE - } - - override fun onStart(conn: FlowConnection, now: Long) { - conn.pull() - } - } - - provider.consume(consumer) - } - - @Test - fun testInterrupt() = runSimulation { - val engine = FlowEngineImpl(coroutineContext, clock) - val capacity = 4200.0 - val provider = FlowSink(engine, capacity) - lateinit var resCtx: FlowConnection - - val consumer = object : FlowSource { - var isFirst = true - - override fun onStart(conn: FlowConnection, now: Long) { - resCtx = conn - } - - override fun onPull(conn: FlowConnection, now: Long): Long { - return if (isFirst) { - isFirst = false - conn.push(1.0) - 4000 - } else { - conn.close() - Long.MAX_VALUE - } - } - } - - launch { - yield() - resCtx.pull() - } - provider.consume(consumer) - - assertEquals(0, clock.millis()) - } - - @Test - fun testFailure() = runSimulation { - val engine = FlowEngineImpl(coroutineContext, clock) - val capacity = 4200.0 - val provider = FlowSink(engine, capacity) - - val consumer = object : FlowSource { - override fun onStart(conn: FlowConnection, now: Long) { - throw IllegalStateException("Hi") - } - - override fun onPull(conn: FlowConnection, now: Long): Long { - return Long.MAX_VALUE - } - } - - assertThrows<IllegalStateException> { - provider.consume(consumer) - } - } - - @Test - fun testExceptionPropagationOnNext() = runSimulation { - val engine = FlowEngineImpl(coroutineContext, clock) - val capacity = 4200.0 - val provider = FlowSink(engine, capacity) - - val consumer = object : FlowSource { - var isFirst = true - - override fun onPull(conn: FlowConnection, now: Long): Long { - return if (isFirst) { - isFirst = false - conn.push(1.0) - 1000 - } else { - throw IllegalStateException() - } - } - } - - assertThrows<IllegalStateException> { - provider.consume(consumer) - } - } - - @Test - fun testConcurrentConsumption() = runSimulation { - val engine = FlowEngineImpl(coroutineContext, clock) - val capacity = 4200.0 - val provider = FlowSink(engine, capacity) - - val consumer = FixedFlowSource(capacity, 1.0) - - assertThrows<IllegalStateException> { - coroutineScope { - launch { provider.consume(consumer) } - provider.consume(consumer) - } - } - } - - @Test - fun testCancelDuringConsumption() = runSimulation { - val engine = FlowEngineImpl(coroutineContext, clock) - val capacity = 4200.0 - val provider = FlowSink(engine, capacity) - - val consumer = FixedFlowSource(capacity, 1.0) - - launch { provider.consume(consumer) } - delay(500) - provider.cancel() - - yield() - - assertEquals(500, clock.millis()) - } - - @Test - fun testInfiniteSleep() { - assertThrows<IllegalStateException> { - runSimulation { - val engine = FlowEngineImpl(coroutineContext, clock) - val capacity = 4200.0 - val provider = FlowSink(engine, capacity) - - val consumer = object : FlowSource { - override fun onPull(conn: FlowConnection, now: Long): Long = Long.MAX_VALUE - } - - provider.consume(consumer) - } - } - } -} diff --git a/opendc-simulator/opendc-simulator-flow/src/test/kotlin/org/opendc/simulator/flow/mux/ForwardingFlowMultiplexerTest.kt b/opendc-simulator/opendc-simulator-flow/src/test/kotlin/org/opendc/simulator/flow/mux/ForwardingFlowMultiplexerTest.kt deleted file mode 100644 index 2409e174..00000000 --- a/opendc-simulator/opendc-simulator-flow/src/test/kotlin/org/opendc/simulator/flow/mux/ForwardingFlowMultiplexerTest.kt +++ /dev/null @@ -1,158 +0,0 @@ -/* - * 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.flow.mux - -import kotlinx.coroutines.yield -import org.junit.jupiter.api.Assertions.assertEquals -import org.junit.jupiter.api.Test -import org.junit.jupiter.api.assertAll -import org.junit.jupiter.api.assertThrows -import org.opendc.simulator.flow.FlowConnection -import org.opendc.simulator.flow.FlowForwarder -import org.opendc.simulator.flow.FlowSink -import org.opendc.simulator.flow.FlowSource -import org.opendc.simulator.flow.consume -import org.opendc.simulator.flow.internal.FlowEngineImpl -import org.opendc.simulator.flow.source.FixedFlowSource -import org.opendc.simulator.flow.source.FlowSourceRateAdapter -import org.opendc.simulator.flow.source.TraceFlowSource -import org.opendc.simulator.kotlin.runSimulation - -/** - * Test suite for the [ForwardingFlowMultiplexer] class. - */ -internal class ForwardingFlowMultiplexerTest { - /** - * Test a trace workload. - */ - @Test - fun testTrace() = runSimulation { - val engine = FlowEngineImpl(coroutineContext, clock) - - val speed = mutableListOf<Double>() - - val duration = 5 * 60L - val workload = - TraceFlowSource( - sequenceOf( - TraceFlowSource.Fragment(duration * 1000, 28.0), - TraceFlowSource.Fragment(duration * 1000, 3500.0), - TraceFlowSource.Fragment(duration * 1000, 0.0), - TraceFlowSource.Fragment(duration * 1000, 183.0) - ) - ) - - val switch = ForwardingFlowMultiplexer(engine) - val source = FlowSink(engine, 3200.0) - val forwarder = FlowForwarder(engine) - val adapter = FlowSourceRateAdapter(forwarder, speed::add) - source.startConsumer(adapter) - forwarder.startConsumer(switch.newOutput()) - - val provider = switch.newInput() - provider.consume(workload) - yield() - - assertAll( - { assertEquals(listOf(0.0, 28.0, 3200.0, 0.0, 183.0, 0.0), speed) { "Correct speed" } }, - { assertEquals(5 * 60L * 4000, clock.millis()) { "Took enough time" } } - ) - } - - /** - * Test runtime workload on hypervisor. - */ - @Test - fun testRuntimeWorkload() = runSimulation { - val engine = FlowEngineImpl(coroutineContext, clock) - - val duration = 5 * 60L * 1000 - val workload = FixedFlowSource(duration * 3.2, 1.0) - - val switch = ForwardingFlowMultiplexer(engine) - val source = FlowSink(engine, 3200.0) - - source.startConsumer(switch.newOutput()) - - val provider = switch.newInput() - provider.consume(workload) - yield() - - assertEquals(duration, clock.millis()) { "Took enough time" } - } - - /** - * Test two workloads running sequentially. - */ - @Test - fun testTwoWorkloads() = runSimulation { - val engine = FlowEngineImpl(coroutineContext, clock) - - val duration = 5 * 60L * 1000 - val workload = object : FlowSource { - var isFirst = true - - override fun onStart(conn: FlowConnection, now: Long) { - isFirst = true - } - - override fun onPull(conn: FlowConnection, now: Long): Long { - return if (isFirst) { - isFirst = false - conn.push(1.0) - duration - } else { - conn.close() - Long.MAX_VALUE - } - } - } - - val switch = ForwardingFlowMultiplexer(engine) - val source = FlowSink(engine, 3200.0) - - source.startConsumer(switch.newOutput()) - - val provider = switch.newInput() - provider.consume(workload) - yield() - provider.consume(workload) - assertEquals(duration * 2, clock.millis()) { "Took enough time" } - } - - /** - * Test concurrent workloads on the machine. - */ - @Test - fun testConcurrentWorkloadFails() = runSimulation { - val engine = FlowEngineImpl(coroutineContext, clock) - - val switch = ForwardingFlowMultiplexer(engine) - val source = FlowSink(engine, 3200.0) - - source.startConsumer(switch.newOutput()) - - switch.newInput() - assertThrows<IllegalStateException> { switch.newInput() } - } -} diff --git a/opendc-simulator/opendc-simulator-flow/src/test/kotlin/org/opendc/simulator/flow/mux/MaxMinFlowMultiplexerTest.kt b/opendc-simulator/opendc-simulator-flow/src/test/kotlin/org/opendc/simulator/flow/mux/MaxMinFlowMultiplexerTest.kt deleted file mode 100644 index a6bf8ad8..00000000 --- a/opendc-simulator/opendc-simulator-flow/src/test/kotlin/org/opendc/simulator/flow/mux/MaxMinFlowMultiplexerTest.kt +++ /dev/null @@ -1,150 +0,0 @@ -/* - * 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.flow.mux - -import kotlinx.coroutines.coroutineScope -import kotlinx.coroutines.launch -import kotlinx.coroutines.yield -import org.junit.jupiter.api.Assertions.assertAll -import org.junit.jupiter.api.Assertions.assertEquals -import org.junit.jupiter.api.Test -import org.opendc.simulator.flow.FlowSink -import org.opendc.simulator.flow.consume -import org.opendc.simulator.flow.internal.FlowEngineImpl -import org.opendc.simulator.flow.source.FixedFlowSource -import org.opendc.simulator.flow.source.TraceFlowSource -import org.opendc.simulator.kotlin.runSimulation - -/** - * Test suite for the [FlowMultiplexer] implementations - */ -internal class MaxMinFlowMultiplexerTest { - @Test - fun testSmoke() = runSimulation { - val scheduler = FlowEngineImpl(coroutineContext, clock) - val switch = MaxMinFlowMultiplexer(scheduler) - - val sources = List(2) { FlowSink(scheduler, 2000.0) } - sources.forEach { it.startConsumer(switch.newOutput()) } - - val provider = switch.newInput() - val consumer = FixedFlowSource(2000.0, 1.0) - - try { - provider.consume(consumer) - yield() - } finally { - switch.clear() - } - } - - /** - * Test overcommitting of resources via the hypervisor with a single VM. - */ - @Test - fun testOvercommittedSingle() = runSimulation { - val scheduler = FlowEngineImpl(coroutineContext, clock) - - val duration = 5 * 60L - val workload = - TraceFlowSource( - sequenceOf( - TraceFlowSource.Fragment(duration * 1000, 28.0), - TraceFlowSource.Fragment(duration * 1000, 3500.0), - TraceFlowSource.Fragment(duration * 1000, 0.0), - TraceFlowSource.Fragment(duration * 1000, 183.0) - ) - ) - - val switch = MaxMinFlowMultiplexer(scheduler) - val sink = FlowSink(scheduler, 3200.0) - val provider = switch.newInput() - - try { - sink.startConsumer(switch.newOutput()) - provider.consume(workload) - yield() - } finally { - switch.clear() - } - - assertAll( - { assertEquals(1113300.0, switch.counters.demand, "Requested work does not match") }, - { assertEquals(1023300.0, switch.counters.actual, "Actual work does not match") }, - { assertEquals(2816700.0, switch.counters.remaining, "Remaining capacity does not match") }, - { assertEquals(1200000, clock.millis()) } - ) - } - - /** - * Test overcommitting of resources via the hypervisor with two VMs. - */ - @Test - fun testOvercommittedDual() = runSimulation { - val scheduler = FlowEngineImpl(coroutineContext, clock) - - val duration = 5 * 60L - val workloadA = - TraceFlowSource( - sequenceOf( - TraceFlowSource.Fragment(duration * 1000, 28.0), - TraceFlowSource.Fragment(duration * 1000, 3500.0), - TraceFlowSource.Fragment(duration * 1000, 0.0), - TraceFlowSource.Fragment(duration * 1000, 183.0) - ) - ) - val workloadB = - TraceFlowSource( - sequenceOf( - TraceFlowSource.Fragment(duration * 1000, 28.0), - TraceFlowSource.Fragment(duration * 1000, 3100.0), - TraceFlowSource.Fragment(duration * 1000, 0.0), - TraceFlowSource.Fragment(duration * 1000, 73.0) - ) - ) - - val switch = MaxMinFlowMultiplexer(scheduler) - val sink = FlowSink(scheduler, 3200.0) - val providerA = switch.newInput() - val providerB = switch.newInput() - - try { - sink.startConsumer(switch.newOutput()) - - coroutineScope { - launch { providerA.consume(workloadA) } - providerB.consume(workloadB) - } - - yield() - } finally { - switch.clear() - } - assertAll( - { assertEquals(2073600.0, switch.counters.demand, "Requested work does not match") }, - { assertEquals(1053600.0, switch.counters.actual, "Granted work does not match") }, - { assertEquals(2786400.0, switch.counters.remaining, "Remaining capacity does not match") }, - { assertEquals(1200000, clock.millis()) } - ) - } -} diff --git a/opendc-simulator/opendc-simulator-flow/src/test/kotlin/org/opendc/simulator/flow2/FlowEngineTest.kt b/opendc-simulator/opendc-simulator-flow/src/test/kotlin/org/opendc/simulator/flow2/FlowEngineTest.kt new file mode 100644 index 00000000..839835ce --- /dev/null +++ b/opendc-simulator/opendc-simulator-flow/src/test/kotlin/org/opendc/simulator/flow2/FlowEngineTest.kt @@ -0,0 +1,197 @@ +/* + * Copyright (c) 2022 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.flow2 + +import io.mockk.mockk +import org.junit.jupiter.api.Assertions.assertEquals +import org.junit.jupiter.api.Assertions.assertNotEquals +import org.junit.jupiter.api.Test +import org.junit.jupiter.api.assertThrows +import org.opendc.simulator.flow2.mux.MaxMinFlowMultiplexer +import org.opendc.simulator.flow2.sink.SimpleFlowSink +import org.opendc.simulator.flow2.source.SimpleFlowSource +import org.opendc.simulator.kotlin.runSimulation + +/** + * Smoke tests for the Flow API. + */ +class FlowEngineTest { + @Test + fun testSmoke() = runSimulation { + val engine = FlowEngine.create(coroutineContext, clock) + val graph = engine.newGraph() + + val multiplexer = MaxMinFlowMultiplexer(graph) + val sink = SimpleFlowSink(graph, 2.0f) + + graph.connect(multiplexer.newOutput(), sink.input) + + val sourceA = SimpleFlowSource(graph, 2000.0f, 0.8f) + val sourceB = SimpleFlowSource(graph, 2000.0f, 0.8f) + + graph.connect(sourceA.output, multiplexer.newInput()) + graph.connect(sourceB.output, multiplexer.newInput()) + } + + @Test + fun testConnectInvalidInlet() = runSimulation { + val engine = FlowEngine.create(coroutineContext, clock) + val graph = engine.newGraph() + + val inlet = mockk<Inlet>() + val source = SimpleFlowSource(graph, 2000.0f, 0.8f) + assertThrows<IllegalArgumentException> { graph.connect(source.output, inlet) } + } + + @Test + fun testConnectInvalidOutlet() = runSimulation { + val engine = FlowEngine.create(coroutineContext, clock) + val graph = engine.newGraph() + + val outlet = mockk<Outlet>() + val sink = SimpleFlowSink(graph, 2.0f) + assertThrows<IllegalArgumentException> { graph.connect(outlet, sink.input) } + } + + @Test + fun testConnectInletBelongsToDifferentGraph() = runSimulation { + val engine = FlowEngine.create(coroutineContext, clock) + val graphA = engine.newGraph() + val graphB = engine.newGraph() + + val sink = SimpleFlowSink(graphB, 2.0f) + val source = SimpleFlowSource(graphA, 2000.0f, 0.8f) + + assertThrows<IllegalArgumentException> { graphA.connect(source.output, sink.input) } + } + + @Test + fun testConnectOutletBelongsToDifferentGraph() = runSimulation { + val engine = FlowEngine.create(coroutineContext, clock) + val graphA = engine.newGraph() + val graphB = engine.newGraph() + + val sink = SimpleFlowSink(graphA, 2.0f) + val source = SimpleFlowSource(graphB, 2000.0f, 0.8f) + + assertThrows<IllegalArgumentException> { graphA.connect(source.output, sink.input) } + } + + @Test + fun testConnectInletAlreadyConnected() = runSimulation { + val engine = FlowEngine.create(coroutineContext, clock) + val graph = engine.newGraph() + + val sink = SimpleFlowSink(graph, 2.0f) + val sourceA = SimpleFlowSource(graph, 2000.0f, 0.8f) + val sourceB = SimpleFlowSource(graph, 2000.0f, 0.8f) + + graph.connect(sourceA.output, sink.input) + assertThrows<IllegalStateException> { graph.connect(sourceB.output, sink.input) } + } + + @Test + fun testConnectOutletAlreadyConnected() = runSimulation { + val engine = FlowEngine.create(coroutineContext, clock) + val graph = engine.newGraph() + + val sinkA = SimpleFlowSink(graph, 2.0f) + val sinkB = SimpleFlowSink(graph, 2.0f) + val source = SimpleFlowSource(graph, 2000.0f, 0.8f) + + graph.connect(source.output, sinkA.input) + assertThrows<IllegalStateException> { graph.connect(source.output, sinkB.input) } + } + + @Test + fun testDisconnectInletInvalid() = runSimulation { + val engine = FlowEngine.create(coroutineContext, clock) + val graph = engine.newGraph() + + val inlet = mockk<Inlet>() + assertThrows<IllegalArgumentException> { graph.disconnect(inlet) } + } + + @Test + fun testDisconnectOutletInvalid() = runSimulation { + val engine = FlowEngine.create(coroutineContext, clock) + val graph = engine.newGraph() + + val outlet = mockk<Outlet>() + assertThrows<IllegalArgumentException> { graph.disconnect(outlet) } + } + + @Test + fun testDisconnectInletInvalidGraph() = runSimulation { + val engine = FlowEngine.create(coroutineContext, clock) + val graphA = engine.newGraph() + val graphB = engine.newGraph() + + val sink = SimpleFlowSink(graphA, 2.0f) + + assertThrows<IllegalArgumentException> { graphB.disconnect(sink.input) } + } + + @Test + fun testDisconnectOutletInvalidGraph() = runSimulation { + val engine = FlowEngine.create(coroutineContext, clock) + val graphA = engine.newGraph() + val graphB = engine.newGraph() + + val source = SimpleFlowSource(graphA, 2000.0f, 0.8f) + + assertThrows<IllegalArgumentException> { graphB.disconnect(source.output) } + } + + @Test + fun testInletEquality() = runSimulation { + val engine = FlowEngine.create(coroutineContext, clock) + val graph = engine.newGraph() + + val sinkA = SimpleFlowSink(graph, 2.0f) + val sinkB = SimpleFlowSink(graph, 2.0f) + + val multiplexer = MaxMinFlowMultiplexer(graph) + + assertEquals(sinkA.input, sinkA.input) + assertNotEquals(sinkA.input, sinkB.input) + + assertNotEquals(multiplexer.newInput(), multiplexer.newInput()) + } + + @Test + fun testOutletEquality() = runSimulation { + val engine = FlowEngine.create(coroutineContext, clock) + val graph = engine.newGraph() + + val sourceA = SimpleFlowSource(graph, 2000.0f, 0.8f) + val sourceB = SimpleFlowSource(graph, 2000.0f, 0.8f) + + val multiplexer = MaxMinFlowMultiplexer(graph) + + assertEquals(sourceA.output, sourceA.output) + assertNotEquals(sourceA.output, sourceB.output) + + assertNotEquals(multiplexer.newOutput(), multiplexer.newOutput()) + } +} diff --git a/opendc-simulator/opendc-simulator-flow/src/test/kotlin/org/opendc/simulator/flow2/FlowTimerQueueTest.kt b/opendc-simulator/opendc-simulator-flow/src/test/kotlin/org/opendc/simulator/flow2/FlowTimerQueueTest.kt new file mode 100644 index 00000000..1824959c --- /dev/null +++ b/opendc-simulator/opendc-simulator-flow/src/test/kotlin/org/opendc/simulator/flow2/FlowTimerQueueTest.kt @@ -0,0 +1,385 @@ +/* + * Copyright (c) 2022 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.flow2 + +import io.mockk.mockk +import org.junit.jupiter.api.Assertions.assertAll +import org.junit.jupiter.api.Assertions.assertEquals +import org.junit.jupiter.api.Assertions.assertNull +import org.junit.jupiter.api.BeforeEach +import org.junit.jupiter.api.Test + +/** + * Test suite for the [FlowTimerQueue] class. + */ +class FlowTimerQueueTest { + private lateinit var queue: FlowTimerQueue + + @BeforeEach + fun setUp() { + queue = FlowTimerQueue(3) + } + + /** + * Test whether a call to [FlowTimerQueue.poll] returns `null` for an empty queue. + */ + @Test + fun testPollEmpty() { + assertAll( + { assertEquals(Long.MAX_VALUE, queue.peekDeadline()) }, + { assertNull(queue.poll(100L)) } + ) + } + + /** + * Test whether a call to [FlowTimerQueue.poll] returns the proper value for a queue with a single entry. + */ + @Test + fun testSingleEntry() { + val entry = mockk<FlowStage>() + entry.deadline = 100 + entry.timerIndex = -1 + + queue.enqueue(entry) + + assertAll( + { assertEquals(100, queue.peekDeadline()) }, + { assertNull(queue.poll(10L)) }, + { assertEquals(entry, queue.poll(200L)) }, + { assertNull(queue.poll(200L)) } + ) + } + + /** + * Test whether [FlowTimerQueue.poll] returns values in the queue in the proper order. + */ + @Test + fun testMultipleEntries() { + val entryA = mockk<FlowStage>() + entryA.deadline = 100 + entryA.timerIndex = -1 + + queue.enqueue(entryA) + + val entryB = mockk<FlowStage>() + entryB.deadline = 10 + entryB.timerIndex = -1 + + queue.enqueue(entryB) + + val entryC = mockk<FlowStage>() + entryC.deadline = 58 + entryC.timerIndex = -1 + + queue.enqueue(entryC) + + assertAll( + { assertEquals(10, queue.peekDeadline()) }, + { assertEquals(entryB, queue.poll(100L)) }, + { assertEquals(entryC, queue.poll(100L)) }, + { assertEquals(entryA, queue.poll(100L)) }, + { assertNull(queue.poll(100L)) } + ) + } + + /** + * Test that the queue is properly resized when the number of entries exceed the capacity. + */ + @Test + fun testResize() { + val entryA = mockk<FlowStage>() + entryA.deadline = 100 + entryA.timerIndex = -1 + + queue.enqueue(entryA) + + val entryB = mockk<FlowStage>() + entryB.deadline = 20 + entryB.timerIndex = -1 + + queue.enqueue(entryB) + + val entryC = mockk<FlowStage>() + entryC.deadline = 58 + entryC.timerIndex = -1 + + queue.enqueue(entryC) + + val entryD = mockk<FlowStage>() + entryD.deadline = 31 + entryD.timerIndex = -1 + + queue.enqueue(entryD) + + assertAll( + { assertEquals(20, queue.peekDeadline()) }, + { assertEquals(entryB, queue.poll(100L)) }, + { assertEquals(entryD, queue.poll(100L)) }, + { assertEquals(entryC, queue.poll(100L)) }, + { assertEquals(entryA, queue.poll(100L)) }, + { assertNull(queue.poll(100L)) } + ) + } + + /** + * Test to verify that we can change the deadline of the last element in the queue. + */ + @Test + fun testChangeDeadlineTail() { + val entryA = mockk<FlowStage>() + entryA.deadline = 100 + entryA.timerIndex = -1 + + queue.enqueue(entryA) + + val entryB = mockk<FlowStage>() + entryB.deadline = 20 + entryB.timerIndex = -1 + + queue.enqueue(entryB) + + val entryC = mockk<FlowStage>() + entryC.deadline = 58 + entryC.timerIndex = -1 + + queue.enqueue(entryC) + + entryA.deadline = 10 + queue.enqueue(entryA) + + assertAll( + { assertEquals(10, queue.peekDeadline()) }, + { assertEquals(entryA, queue.poll(100L)) }, + { assertEquals(entryB, queue.poll(100L)) }, + { assertEquals(entryC, queue.poll(100L)) }, + { assertNull(queue.poll(100L)) } + ) + } + + /** + * Test that we can change the deadline of the head entry in the queue. + */ + @Test + fun testChangeDeadlineMiddle() { + val entryA = mockk<FlowStage>() + entryA.deadline = 100 + entryA.timerIndex = -1 + + queue.enqueue(entryA) + + val entryB = mockk<FlowStage>() + entryB.deadline = 20 + entryB.timerIndex = -1 + + queue.enqueue(entryB) + + val entryC = mockk<FlowStage>() + entryC.deadline = 58 + entryC.timerIndex = -1 + + queue.enqueue(entryC) + + entryC.deadline = 10 + queue.enqueue(entryC) + + assertAll( + { assertEquals(10, queue.peekDeadline()) }, + { assertEquals(entryC, queue.poll(100L)) }, + { assertEquals(entryB, queue.poll(100L)) }, + { assertEquals(entryA, queue.poll(100L)) }, + { assertNull(queue.poll(100L)) } + ) + } + + /** + * Test that we can change the deadline of the head entry in the queue. + */ + @Test + fun testChangeDeadlineHead() { + val entryA = mockk<FlowStage>() + entryA.deadline = 100 + entryA.timerIndex = -1 + + queue.enqueue(entryA) + + val entryB = mockk<FlowStage>() + entryB.deadline = 20 + entryB.timerIndex = -1 + + queue.enqueue(entryB) + + val entryC = mockk<FlowStage>() + entryC.deadline = 58 + entryC.timerIndex = -1 + + queue.enqueue(entryC) + + entryB.deadline = 30 + queue.enqueue(entryB) + + assertAll( + { assertEquals(30, queue.peekDeadline()) }, + { assertEquals(entryB, queue.poll(100L)) }, + { assertEquals(entryC, queue.poll(100L)) }, + { assertEquals(entryA, queue.poll(100L)) }, + { assertNull(queue.poll(100L)) } + ) + } + + /** + * Test that an unchanged deadline results in a no-op. + */ + @Test + fun testChangeDeadlineNop() { + val entryA = mockk<FlowStage>() + entryA.deadline = 100 + entryA.timerIndex = -1 + + queue.enqueue(entryA) + + val entryB = mockk<FlowStage>() + entryB.deadline = 20 + entryB.timerIndex = -1 + + queue.enqueue(entryB) + + val entryC = mockk<FlowStage>() + entryC.deadline = 58 + entryC.timerIndex = -1 + + queue.enqueue(entryC) + + // Should be a no-op + queue.enqueue(entryA) + + assertAll( + { assertEquals(20, queue.peekDeadline()) }, + { assertEquals(entryB, queue.poll(100L)) }, + { assertEquals(entryC, queue.poll(100L)) }, + { assertEquals(entryA, queue.poll(100L)) }, + { assertNull(queue.poll(100L)) } + ) + } + + /** + * Test that we can remove an entry from the end of the queue. + */ + @Test + fun testRemoveEntryTail() { + val entryA = mockk<FlowStage>() + entryA.deadline = 100 + entryA.timerIndex = -1 + + queue.enqueue(entryA) + + val entryB = mockk<FlowStage>() + entryB.deadline = 20 + entryB.timerIndex = -1 + + queue.enqueue(entryB) + + val entryC = mockk<FlowStage>() + entryC.deadline = 58 + entryC.timerIndex = -1 + + queue.enqueue(entryC) + + entryC.deadline = Long.MAX_VALUE + queue.enqueue(entryC) + + assertAll( + { assertEquals(20, queue.peekDeadline()) }, + { assertEquals(entryB, queue.poll(100L)) }, + { assertEquals(entryA, queue.poll(100L)) }, + { assertNull(queue.poll(100L)) } + ) + } + + /** + * Test that we can remove an entry from the head of the queue. + */ + @Test + fun testRemoveEntryHead() { + val entryA = mockk<FlowStage>() + entryA.deadline = 100 + entryA.timerIndex = -1 + + queue.enqueue(entryA) + + val entryB = mockk<FlowStage>() + entryB.deadline = 20 + entryB.timerIndex = -1 + + queue.enqueue(entryB) + + val entryC = mockk<FlowStage>() + entryC.deadline = 58 + entryC.timerIndex = -1 + + queue.enqueue(entryC) + + entryB.deadline = Long.MAX_VALUE + queue.enqueue(entryB) + + assertAll( + { assertEquals(58, queue.peekDeadline()) }, + { assertEquals(entryC, queue.poll(100L)) }, + { assertEquals(entryA, queue.poll(100L)) }, + { assertNull(queue.poll(100L)) } + ) + } + + /** + * Test that we can remove an entry from the middle of a queue. + */ + @Test + fun testRemoveEntryMiddle() { + val entryA = mockk<FlowStage>() + entryA.deadline = 100 + entryA.timerIndex = -1 + + queue.enqueue(entryA) + + val entryB = mockk<FlowStage>() + entryB.deadline = 20 + entryB.timerIndex = -1 + + queue.enqueue(entryB) + + val entryC = mockk<FlowStage>() + entryC.deadline = 58 + entryC.timerIndex = -1 + + queue.enqueue(entryC) + + entryC.deadline = Long.MAX_VALUE + queue.enqueue(entryC) + + assertAll( + { assertEquals(20, queue.peekDeadline()) }, + { assertEquals(entryB, queue.poll(100L)) }, + { assertEquals(entryA, queue.poll(100L)) }, + { assertNull(queue.poll(100L)) } + ) + } +} diff --git a/opendc-simulator/opendc-simulator-flow/src/test/kotlin/org/opendc/simulator/flow2/InvocationStackTest.kt b/opendc-simulator/opendc-simulator-flow/src/test/kotlin/org/opendc/simulator/flow2/InvocationStackTest.kt new file mode 100644 index 00000000..2250fe87 --- /dev/null +++ b/opendc-simulator/opendc-simulator-flow/src/test/kotlin/org/opendc/simulator/flow2/InvocationStackTest.kt @@ -0,0 +1,71 @@ +/* + * Copyright (c) 2022 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.flow2 + +import org.junit.jupiter.api.Assertions.assertEquals +import org.junit.jupiter.api.Assertions.assertFalse +import org.junit.jupiter.api.Assertions.assertTrue +import org.junit.jupiter.api.Test + +/** + * Test suite for the [InvocationStack] class. + */ +class InvocationStackTest { + private val stack = InvocationStack(2) + + @Test + fun testPollEmpty() { + assertEquals(Long.MAX_VALUE, stack.poll()) + } + + @Test + fun testAddSingle() { + assertTrue(stack.tryAdd(10)) + assertEquals(10, stack.poll()) + } + + @Test + fun testAddLater() { + assertTrue(stack.tryAdd(10)) + assertFalse(stack.tryAdd(15)) + assertEquals(10, stack.poll()) + } + + @Test + fun testAddEarlier() { + assertTrue(stack.tryAdd(10)) + assertTrue(stack.tryAdd(5)) + assertEquals(5, stack.poll()) + assertEquals(10, stack.poll()) + } + + @Test + fun testCapacityExceeded() { + assertTrue(stack.tryAdd(10)) + assertTrue(stack.tryAdd(5)) + assertTrue(stack.tryAdd(2)) + assertEquals(2, stack.poll()) + assertEquals(5, stack.poll()) + assertEquals(10, stack.poll()) + } +} diff --git a/opendc-simulator/opendc-simulator-flow/src/test/kotlin/org/opendc/simulator/flow2/mux/ForwardingFlowMultiplexerTest.kt b/opendc-simulator/opendc-simulator-flow/src/test/kotlin/org/opendc/simulator/flow2/mux/ForwardingFlowMultiplexerTest.kt new file mode 100644 index 00000000..a2ed2195 --- /dev/null +++ b/opendc-simulator/opendc-simulator-flow/src/test/kotlin/org/opendc/simulator/flow2/mux/ForwardingFlowMultiplexerTest.kt @@ -0,0 +1,66 @@ +/* + * Copyright (c) 2022 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.flow2.mux + +import org.junit.jupiter.api.Assertions.assertEquals +import org.junit.jupiter.api.Test +import org.junit.jupiter.api.assertAll +import org.opendc.simulator.flow2.FlowEngine +import org.opendc.simulator.flow2.sink.SimpleFlowSink +import org.opendc.simulator.flow2.source.TraceFlowSource +import org.opendc.simulator.kotlin.runSimulation + +/** + * Test suite for the [ForwardingFlowMultiplexer] class. + */ +class ForwardingFlowMultiplexerTest { + /** + * Test a trace workload. + */ + @Test + fun testTrace() = runSimulation { + val engine = FlowEngine.create(coroutineContext, clock) + val graph = engine.newGraph() + + val switch = ForwardingFlowMultiplexer(graph) + val sink = SimpleFlowSink(graph, 3200.0f) + graph.connect(switch.newOutput(), sink.input) + + val workload = + TraceFlowSource( + graph, + TraceFlowSource.Trace( + longArrayOf(1000, 2000, 3000, 4000), + floatArrayOf(28.0f, 3500.0f, 0.0f, 183.0f), + 4 + ) + ) + graph.connect(workload.output, switch.newInput()) + + advanceUntilIdle() + + assertAll( + { assertEquals(4000, clock.millis()) { "Took enough time" } } + ) + } +} diff --git a/opendc-simulator/opendc-simulator-flow/src/test/kotlin/org/opendc/simulator/flow/source/FixedFlowSourceTest.kt b/opendc-simulator/opendc-simulator-flow/src/test/kotlin/org/opendc/simulator/flow2/mux/MaxMinFlowMultiplexerTest.kt index 552579ff..ba339ee3 100644 --- a/opendc-simulator/opendc-simulator-flow/src/test/kotlin/org/opendc/simulator/flow/source/FixedFlowSourceTest.kt +++ b/opendc-simulator/opendc-simulator-flow/src/test/kotlin/org/opendc/simulator/flow2/mux/MaxMinFlowMultiplexerTest.kt @@ -1,5 +1,5 @@ /* - * Copyright (c) 2021 AtLarge Research + * Copyright (c) 2022 AtLarge Research * * Permission is hereby granted, free of charge, to any person obtaining a copy * of this software and associated documentation files (the "Software"), to deal @@ -20,38 +20,35 @@ * SOFTWARE. */ -package org.opendc.simulator.flow.source +package org.opendc.simulator.flow2.mux import org.junit.jupiter.api.Assertions.assertEquals import org.junit.jupiter.api.Test -import org.opendc.simulator.flow.FlowSink -import org.opendc.simulator.flow.consume -import org.opendc.simulator.flow.internal.FlowEngineImpl +import org.opendc.simulator.flow2.FlowEngine +import org.opendc.simulator.flow2.sink.SimpleFlowSink +import org.opendc.simulator.flow2.source.SimpleFlowSource import org.opendc.simulator.kotlin.runSimulation /** - * A test suite for the [FixedFlowSource] class. + * Test suite for the [MaxMinFlowMultiplexer] class. */ -internal class FixedFlowSourceTest { +class MaxMinFlowMultiplexerTest { @Test fun testSmoke() = runSimulation { - val scheduler = FlowEngineImpl(coroutineContext, clock) - val provider = FlowSink(scheduler, 1.0) + val engine = FlowEngine.create(coroutineContext, clock) + val graph = engine.newGraph() + val switch = MaxMinFlowMultiplexer(graph) - val consumer = FixedFlowSource(1.0, 1.0) + val sinks = List(2) { SimpleFlowSink(graph, 2000.0f) } + for (source in sinks) { + graph.connect(switch.newOutput(), source.input) + } - provider.consume(consumer) - assertEquals(1000, clock.millis()) - } - - @Test - fun testUtilization() = runSimulation { - val scheduler = FlowEngineImpl(coroutineContext, clock) - val provider = FlowSink(scheduler, 1.0) + val source = SimpleFlowSource(graph, 2000.0f, 1.0f) + graph.connect(source.output, switch.newInput()) - val consumer = FixedFlowSource(1.0, 0.5) + advanceUntilIdle() - provider.consume(consumer) - assertEquals(2000, clock.millis()) + assertEquals(500, clock.millis()) } } diff --git a/opendc-simulator/opendc-simulator-flow/src/test/kotlin/org/opendc/simulator/flow2/sink/FlowSinkTest.kt b/opendc-simulator/opendc-simulator-flow/src/test/kotlin/org/opendc/simulator/flow2/sink/FlowSinkTest.kt new file mode 100644 index 00000000..a75efba3 --- /dev/null +++ b/opendc-simulator/opendc-simulator-flow/src/test/kotlin/org/opendc/simulator/flow2/sink/FlowSinkTest.kt @@ -0,0 +1,124 @@ +/* + * Copyright (c) 2022 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.flow2.sink + +import kotlinx.coroutines.delay +import org.junit.jupiter.api.Assertions.assertEquals +import org.junit.jupiter.api.Test +import org.opendc.simulator.flow2.FlowEngine +import org.opendc.simulator.flow2.source.SimpleFlowSource +import org.opendc.simulator.flow2.source.TraceFlowSource +import org.opendc.simulator.kotlin.runSimulation +import java.util.concurrent.ThreadLocalRandom + +/** + * Test suite for the [SimpleFlowSink] class. + */ +class FlowSinkTest { + @Test + fun testSmoke() = runSimulation { + val engine = FlowEngine.create(coroutineContext, clock) + val graph = engine.newGraph() + + val sink = SimpleFlowSink(graph, 1.0f) + val source = SimpleFlowSource(graph, 2.0f, 1.0f) + + graph.connect(source.output, sink.input) + advanceUntilIdle() + + assertEquals(2000, clock.millis()) + } + + @Test + fun testAdjustCapacity() = runSimulation { + val engine = FlowEngine.create(coroutineContext, clock) + val graph = engine.newGraph() + + val sink = SimpleFlowSink(graph, 1.0f) + val source = SimpleFlowSource(graph, 2.0f, 1.0f) + + graph.connect(source.output, sink.input) + + delay(1000) + sink.capacity = 0.5f + + advanceUntilIdle() + + assertEquals(3000, clock.millis()) + } + + @Test + fun testUtilization() = runSimulation { + val engine = FlowEngine.create(coroutineContext, clock) + val graph = engine.newGraph() + + val sink = SimpleFlowSink(graph, 1.0f) + val source = SimpleFlowSource(graph, 2.0f, 0.5f) + + graph.connect(source.output, sink.input) + advanceUntilIdle() + + assertEquals(4000, clock.millis()) + } + + @Test + fun testFragments() = runSimulation { + val engine = FlowEngine.create(coroutineContext, clock) + val graph = engine.newGraph() + + val sink = SimpleFlowSink(graph, 1.0f) + val trace = TraceFlowSource.Trace( + longArrayOf(1000, 2000, 3000, 4000), + floatArrayOf(1.0f, 0.5f, 2.0f, 1.0f), + 4 + ) + val source = TraceFlowSource( + graph, + trace + ) + + graph.connect(source.output, sink.input) + advanceUntilIdle() + + assertEquals(4000, clock.millis()) + } + + @Test + fun benchmarkSink() { + val random = ThreadLocalRandom.current() + val traceSize = 10000000 + val trace = TraceFlowSource.Trace( + LongArray(traceSize) { it * 1000L }, + FloatArray(traceSize) { random.nextDouble(0.0, 4500.0).toFloat() }, + traceSize + ) + + return runSimulation { + val engine = FlowEngine.create(coroutineContext, clock) + val graph = engine.newGraph() + val sink = SimpleFlowSink(graph, 4200.0f) + val source = TraceFlowSource(graph, trace) + graph.connect(source.output, sink.input) + } + } +} diff --git a/opendc-simulator/opendc-simulator-network/src/main/java/org/opendc/simulator/network/SimNetworkLink.java b/opendc-simulator/opendc-simulator-network/src/main/java/org/opendc/simulator/network/SimNetworkLink.java new file mode 100644 index 00000000..1ea9cb0e --- /dev/null +++ b/opendc-simulator/opendc-simulator-network/src/main/java/org/opendc/simulator/network/SimNetworkLink.java @@ -0,0 +1,77 @@ +/* + * Copyright (c) 2022 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.network; + +/** + * A physical bidirectional communication link between two [SimNetworkPort]s. + */ +public final class SimNetworkLink { + private final SimNetworkPort left; + private final SimNetworkPort right; + + SimNetworkLink(SimNetworkPort left, SimNetworkPort right) { + this.left = left; + this.right = right; + } + + /** + * Determine whether the specified <code>port</code> participates in this network link. + * + * @return <code>true</code> if the port participates in this link, <code>false</code> otherwise. + */ + public boolean contains(SimNetworkPort port) { + return port == left || port == right; + } + + /** + * Obtain the opposite port to which the specified <code>port</code> is connected through this link. + */ + public SimNetworkPort opposite(SimNetworkPort port) { + if (port == left) { + return right; + } else if (port == right) { + return left; + } + + throw new IllegalArgumentException("Invalid port given"); + } + + /** + * Return the first port of the link. + */ + public SimNetworkPort getLeft() { + return left; + } + + /** + * Return the second port of the link. + */ + public SimNetworkPort getRight() { + return right; + } + + @Override + public String toString() { + return "SimNetworkLink[left=" + left + ",right=" + right + "]"; + } +} diff --git a/opendc-simulator/opendc-simulator-network/src/main/java/org/opendc/simulator/network/SimNetworkPort.java b/opendc-simulator/opendc-simulator-network/src/main/java/org/opendc/simulator/network/SimNetworkPort.java new file mode 100644 index 00000000..b5e09b9b --- /dev/null +++ b/opendc-simulator/opendc-simulator-network/src/main/java/org/opendc/simulator/network/SimNetworkPort.java @@ -0,0 +1,110 @@ +/* + * Copyright (c) 2022 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.network; + +import org.opendc.simulator.flow2.Inlet; +import org.opendc.simulator.flow2.Outlet; + +/** + * A network port allows network devices to be connected to network through links. + */ +public abstract class SimNetworkPort { + SimNetworkLink link; + + /** + * Determine whether the network port is connected to another port. + * + * @return <code>true</code> if the network port is connected, <code>false</code> otherwise. + */ + public boolean isConnected() { + return link != null; + } + + /** + * Return network link which connects this port to another port. + */ + public SimNetworkLink getLink() { + return link; + } + + /** + * Connect this port to the specified <code>port</code>. + */ + public void connect(SimNetworkPort port) { + if (port == this) { + throw new IllegalArgumentException("Circular reference"); + } + if (isConnected()) { + throw new IllegalStateException("Port already connected"); + } + if (port.isConnected()) { + throw new IllegalStateException("Target port already connected"); + } + + final SimNetworkLink link = new SimNetworkLink(this, port); + this.link = link; + port.link = link; + + // Start bidirectional flow channel between the two ports + final Outlet outlet = getOutlet(); + final Inlet inlet = getInlet(); + + outlet.getGraph().connect(outlet, port.getInlet()); + inlet.getGraph().connect(port.getOutlet(), inlet); + } + + /** + * Disconnect the current network link if it exists. + */ + public void disconnect() { + final SimNetworkLink link = this.link; + if (link == null) { + return; + } + + final SimNetworkPort opposite = link.opposite(this); + this.link = null; + opposite.link = null; + + final Outlet outlet = getOutlet(); + final Inlet inlet = getInlet(); + + outlet.getGraph().disconnect(outlet); + inlet.getGraph().disconnect(inlet); + } + + /** + * Return the {@link Outlet} representing the outgoing traffic of this port. + */ + protected abstract Outlet getOutlet(); + + /** + * An [Inlet] representing the ingoing traffic of this port. + */ + protected abstract Inlet getInlet(); + + @Override + public String toString() { + return "SimNetworkPort[isConnected=" + isConnected() + "]"; + } +} diff --git a/opendc-simulator/opendc-simulator-network/src/main/java/org/opendc/simulator/network/SimNetworkSink.java b/opendc-simulator/opendc-simulator-network/src/main/java/org/opendc/simulator/network/SimNetworkSink.java new file mode 100644 index 00000000..f8918328 --- /dev/null +++ b/opendc-simulator/opendc-simulator-network/src/main/java/org/opendc/simulator/network/SimNetworkSink.java @@ -0,0 +1,70 @@ +/* + * Copyright (c) 2022 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.network; + +import org.opendc.simulator.flow2.FlowGraph; +import org.opendc.simulator.flow2.Inlet; +import org.opendc.simulator.flow2.Outlet; +import org.opendc.simulator.flow2.sink.SimpleFlowSink; +import org.opendc.simulator.flow2.source.EmptyFlowSource; + +/** + * A network sink which discards all received traffic and does not generate any traffic itself. + */ +public final class SimNetworkSink extends SimNetworkPort { + private final EmptyFlowSource source; + private final SimpleFlowSink sink; + + /** + * Construct a {@link SimNetworkSink} instance. + * + * @param graph The {@link FlowGraph} to which the sink belongs. + * @param capacity The capacity of the sink in terms of processed data. + */ + public SimNetworkSink(FlowGraph graph, float capacity) { + this.source = new EmptyFlowSource(graph); + this.sink = new SimpleFlowSink(graph, capacity); + } + + /** + * Return the capacity of the sink. + */ + public float getCapacity() { + return sink.getCapacity(); + } + + @Override + protected Outlet getOutlet() { + return source.getOutput(); + } + + @Override + protected Inlet getInlet() { + return sink.getInput(); + } + + @Override + public String toString() { + return "SimNetworkSink[capacity=" + getCapacity() + "]"; + } +} diff --git a/opendc-simulator/opendc-simulator-network/src/main/kotlin/org/opendc/simulator/network/SimNetworkSwitch.kt b/opendc-simulator/opendc-simulator-network/src/main/java/org/opendc/simulator/network/SimNetworkSwitch.java index 7dc249ab..b05dc53d 100644 --- a/opendc-simulator/opendc-simulator-network/src/main/kotlin/org/opendc/simulator/network/SimNetworkSwitch.kt +++ b/opendc-simulator/opendc-simulator-network/src/main/java/org/opendc/simulator/network/SimNetworkSwitch.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2021 AtLarge Research + * Copyright (c) 2022 AtLarge Research * * Permission is hereby granted, free of charge, to any person obtaining a copy * of this software and associated documentation files (the "Software"), to deal @@ -20,14 +20,16 @@ * SOFTWARE. */ -package org.opendc.simulator.network +package org.opendc.simulator.network; + +import java.util.List; /** * A network device connects devices on a network by switching the traffic over its ports. */ public interface SimNetworkSwitch { /** - * The ports of the switch. + * Return the ports of the switch. */ - public val ports: List<SimNetworkPort> + List<SimNetworkPort> getPorts(); } diff --git a/opendc-simulator/opendc-simulator-network/src/main/java/org/opendc/simulator/network/SimNetworkSwitchVirtual.java b/opendc-simulator/opendc-simulator-network/src/main/java/org/opendc/simulator/network/SimNetworkSwitchVirtual.java new file mode 100644 index 00000000..a94bf799 --- /dev/null +++ b/opendc-simulator/opendc-simulator-network/src/main/java/org/opendc/simulator/network/SimNetworkSwitchVirtual.java @@ -0,0 +1,107 @@ +/* + * Copyright (c) 2022 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.network; + +import java.util.ArrayList; +import java.util.Collections; +import java.util.List; +import org.opendc.simulator.flow2.FlowGraph; +import org.opendc.simulator.flow2.Inlet; +import org.opendc.simulator.flow2.Outlet; +import org.opendc.simulator.flow2.mux.FlowMultiplexer; +import org.opendc.simulator.flow2.mux.MaxMinFlowMultiplexer; + +/** + * A {@link SimNetworkSwitch} that can support new networking ports on demand. + */ +public final class SimNetworkSwitchVirtual implements SimNetworkSwitch { + private final List<Port> ports = new ArrayList<>(); + + /** + * The {@link MaxMinFlowMultiplexer} to actually perform the switching. + */ + private final MaxMinFlowMultiplexer mux; + + /** + * Construct a {@link SimNetworkSwitchVirtual} instance. + * + * @param graph The {@link FlowGraph} to drive the simulation. + */ + public SimNetworkSwitchVirtual(FlowGraph graph) { + this.mux = new MaxMinFlowMultiplexer(graph); + } + + /** + * Open a new port on the switch. + */ + public Port newPort() { + final Port port = new Port(mux); + ports.add(port); + return port; + } + + @Override + public List<SimNetworkPort> getPorts() { + return Collections.unmodifiableList(ports); + } + + /** + * A port on the network switch. + */ + public class Port extends SimNetworkPort implements AutoCloseable { + private final FlowMultiplexer mux; + private final Inlet inlet; + private final Outlet outlet; + private boolean isClosed; + + private Port(FlowMultiplexer mux) { + this.mux = mux; + this.inlet = mux.newInput(); + this.outlet = mux.newOutput(); + } + + @Override + protected Outlet getOutlet() { + if (isClosed) { + throw new IllegalStateException("Port is closed"); + } + return outlet; + } + + @Override + protected Inlet getInlet() { + if (isClosed) { + throw new IllegalStateException("Port is closed"); + } + return inlet; + } + + @Override + public void close() { + isClosed = true; + mux.releaseInput(inlet); + mux.releaseOutput(outlet); + ports.remove(this); + } + } +} diff --git a/opendc-simulator/opendc-simulator-network/src/main/kotlin/org/opendc/simulator/network/SimNetworkLink.kt b/opendc-simulator/opendc-simulator-network/src/main/kotlin/org/opendc/simulator/network/SimNetworkLink.kt deleted file mode 100644 index 67562640..00000000 --- a/opendc-simulator/opendc-simulator-network/src/main/kotlin/org/opendc/simulator/network/SimNetworkLink.kt +++ /dev/null @@ -1,49 +0,0 @@ -/* - * 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.network - -/** - * A physical bi-directional communication link between two [SimNetworkPort]s. - * - * @param left The first port of the link. - * @param right The second port of the link. - */ -public class SimNetworkLink(public val left: SimNetworkPort, public val right: SimNetworkPort) { - /** - * Determine whether the specified [port] participates in this network link. - */ - public operator fun contains(port: SimNetworkPort): Boolean = port == left || port == right - - /** - * Obtain the opposite port to which the specified [port] is connected through this link. - */ - public fun opposite(port: SimNetworkPort): SimNetworkPort { - return when (port) { - left -> right - right -> left - else -> throw IllegalArgumentException("Invalid port given") - } - } - - override fun toString(): String = "SimNetworkLink[left=$left,right=$right]" -} diff --git a/opendc-simulator/opendc-simulator-network/src/main/kotlin/org/opendc/simulator/network/SimNetworkPort.kt b/opendc-simulator/opendc-simulator-network/src/main/kotlin/org/opendc/simulator/network/SimNetworkPort.kt deleted file mode 100644 index 4b66d5cf..00000000 --- a/opendc-simulator/opendc-simulator-network/src/main/kotlin/org/opendc/simulator/network/SimNetworkPort.kt +++ /dev/null @@ -1,91 +0,0 @@ -/* - * 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.network - -import org.opendc.simulator.flow.FlowConsumer -import org.opendc.simulator.flow.FlowSource - -/** - * A network port allows network devices to be connected to network through links. - */ -public abstract class SimNetworkPort { - /** - * A flag to indicate that the network port is connected to another port. - */ - public val isConnected: Boolean - get() = _link != null - - /** - * The network link which connects this port to another port. - */ - public val link: SimNetworkLink? - get() = _link - private var _link: SimNetworkLink? = null - - /** - * Connect this port to the specified [port]. - */ - public fun connect(port: SimNetworkPort) { - require(port !== this) { "Circular reference" } - check(!isConnected) { "Port already connected" } - check(!port.isConnected) { "Target port already connected" } - - val link = SimNetworkLink(this, port) - _link = link - port._link = link - - // Start bi-directional flow channel between the two ports - try { - provider.startConsumer(port.createConsumer()) - port.provider.startConsumer(createConsumer()) - } catch (e: Throwable) { - disconnect() - throw e - } - } - - /** - * Disconnect the current network link if it exists. - */ - public fun disconnect() { - val link = _link ?: return - val opposite = link.opposite(this) - _link = null - opposite._link = null - - provider.cancel() - opposite.provider.cancel() - } - - /** - * Create a [FlowSource] which generates the outgoing traffic of this port. - */ - protected abstract fun createConsumer(): FlowSource - - /** - * The [FlowConsumer] which processes the ingoing traffic of this port. - */ - protected abstract val provider: FlowConsumer - - override fun toString(): String = "SimNetworkPort[isConnected=$isConnected]" -} diff --git a/opendc-simulator/opendc-simulator-network/src/main/kotlin/org/opendc/simulator/network/SimNetworkSwitchVirtual.kt b/opendc-simulator/opendc-simulator-network/src/main/kotlin/org/opendc/simulator/network/SimNetworkSwitchVirtual.kt deleted file mode 100644 index c59c44f1..00000000 --- a/opendc-simulator/opendc-simulator-network/src/main/kotlin/org/opendc/simulator/network/SimNetworkSwitchVirtual.kt +++ /dev/null @@ -1,78 +0,0 @@ -/* - * 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.network - -import org.opendc.simulator.flow.FlowConsumer -import org.opendc.simulator.flow.FlowEngine -import org.opendc.simulator.flow.FlowSource -import org.opendc.simulator.flow.mux.MaxMinFlowMultiplexer - -/** - * A [SimNetworkSwitch] that can support new networking ports on demand. - */ -public class SimNetworkSwitchVirtual(private val engine: FlowEngine) : SimNetworkSwitch { - /** - * The ports of this switch. - */ - override val ports: List<Port> - get() = _ports - private val _ports = mutableListOf<Port>() - - /** - * The [MaxMinFlowMultiplexer] to actually perform the switching. - */ - private val mux = MaxMinFlowMultiplexer(engine) - - /** - * Open a new port on the switch. - */ - public fun newPort(): Port { - val port = Port() - _ports.add(port) - return port - } - - /** - * A port on the network switch. - */ - public inner class Port : SimNetworkPort(), AutoCloseable { - /** - * A flag to indicate that this virtual port was removed from the switch. - */ - private var isClosed: Boolean = false - - override val provider: FlowConsumer - get() = _provider - private val _provider = mux.newInput() - - private val _source = mux.newOutput() - - override fun createConsumer(): FlowSource = _source - - override fun close() { - isClosed = true - mux.removeInput(_provider) - _ports.remove(this) - } - } -} diff --git a/opendc-simulator/opendc-simulator-network/src/test/kotlin/org/opendc/simulator/network/SimNetworkSinkTest.kt b/opendc-simulator/opendc-simulator-network/src/test/kotlin/org/opendc/simulator/network/SimNetworkSinkTest.kt index 78bd533d..8b4ebb89 100644 --- a/opendc-simulator/opendc-simulator-network/src/test/kotlin/org/opendc/simulator/network/SimNetworkSinkTest.kt +++ b/opendc-simulator/opendc-simulator-network/src/test/kotlin/org/opendc/simulator/network/SimNetworkSinkTest.kt @@ -24,8 +24,9 @@ package org.opendc.simulator.network import io.mockk.every import io.mockk.mockk -import io.mockk.spyk import io.mockk.verify +import kotlinx.coroutines.yield +import org.junit.jupiter.api.Assertions.assertAll import org.junit.jupiter.api.Assertions.assertEquals import org.junit.jupiter.api.Assertions.assertFalse import org.junit.jupiter.api.Assertions.assertNull @@ -33,11 +34,7 @@ import org.junit.jupiter.api.Assertions.assertTrue import org.junit.jupiter.api.Test import org.junit.jupiter.api.assertDoesNotThrow import org.junit.jupiter.api.assertThrows -import org.opendc.simulator.flow.FlowConsumer -import org.opendc.simulator.flow.FlowEngine -import org.opendc.simulator.flow.FlowSink -import org.opendc.simulator.flow.FlowSource -import org.opendc.simulator.flow.source.FixedFlowSource +import org.opendc.simulator.flow2.FlowEngine import org.opendc.simulator.kotlin.runSimulation /** @@ -46,18 +43,22 @@ import org.opendc.simulator.kotlin.runSimulation class SimNetworkSinkTest { @Test fun testInitialState() = runSimulation { - val engine = FlowEngine(coroutineContext, clock) - val sink = SimNetworkSink(engine, capacity = 100.0) - - assertFalse(sink.isConnected) - assertNull(sink.link) - assertEquals(100.0, sink.capacity) + val engine = FlowEngine.create(coroutineContext, clock) + val graph = engine.newGraph() + val sink = SimNetworkSink(graph, /*capacity*/ 100.0f) + + assertAll( + { assertFalse(sink.isConnected) }, + { assertNull(sink.link) }, + { assertEquals(100.0f, sink.capacity) } + ) } @Test fun testDisconnectIdempotent() = runSimulation { - val engine = FlowEngine(coroutineContext, clock) - val sink = SimNetworkSink(engine, capacity = 100.0) + val engine = FlowEngine.create(coroutineContext, clock) + val graph = engine.newGraph() + val sink = SimNetworkSink(graph, /*capacity*/ 100.0f) assertDoesNotThrow { sink.disconnect() } assertFalse(sink.isConnected) @@ -65,8 +66,9 @@ class SimNetworkSinkTest { @Test fun testConnectCircular() = runSimulation { - val engine = FlowEngine(coroutineContext, clock) - val sink = SimNetworkSink(engine, capacity = 100.0) + val engine = FlowEngine.create(coroutineContext, clock) + val graph = engine.newGraph() + val sink = SimNetworkSink(graph, /*capacity*/ 100.0f) assertThrows<IllegalArgumentException> { sink.connect(sink) @@ -75,8 +77,9 @@ class SimNetworkSinkTest { @Test fun testConnectAlreadyConnectedTarget() = runSimulation { - val engine = FlowEngine(coroutineContext, clock) - val sink = SimNetworkSink(engine, capacity = 100.0) + val engine = FlowEngine.create(coroutineContext, clock) + val graph = engine.newGraph() + val sink = SimNetworkSink(graph, /*capacity*/ 100.0f) val source = mockk<SimNetworkPort>(relaxUnitFun = true) every { source.isConnected } returns true @@ -87,9 +90,10 @@ class SimNetworkSinkTest { @Test fun testConnectAlreadyConnected() = runSimulation { - val engine = FlowEngine(coroutineContext, clock) - val sink = SimNetworkSink(engine, capacity = 100.0) - val source1 = Source(engine) + val engine = FlowEngine.create(coroutineContext, clock) + val graph = engine.newGraph() + val sink = SimNetworkSink(graph, /*capacity*/ 100.0f) + val source1 = TestSource(graph) val source2 = mockk<SimNetworkPort>(relaxUnitFun = true) @@ -103,41 +107,40 @@ class SimNetworkSinkTest { @Test fun testConnect() = runSimulation { - val engine = FlowEngine(coroutineContext, clock) - val sink = SimNetworkSink(engine, capacity = 100.0) - val source = spyk(Source(engine)) - val consumer = source.consumer + val engine = FlowEngine.create(coroutineContext, clock) + val graph = engine.newGraph() + val sink = SimNetworkSink(graph, /*capacity*/ 100.0f) + val source = TestSource(graph) sink.connect(source) - assertTrue(sink.isConnected) - assertTrue(source.isConnected) + yield() - verify { source.createConsumer() } - verify { consumer.onStart(any(), any()) } + assertAll( + { assertTrue(sink.isConnected) }, + { assertTrue(source.isConnected) }, + { assertEquals(100.0f, source.outlet.capacity) } + ) + + verify { source.logic.onUpdate(any(), any()) } } @Test fun testDisconnect() = runSimulation { - val engine = FlowEngine(coroutineContext, clock) - val sink = SimNetworkSink(engine, capacity = 100.0) - val source = spyk(Source(engine)) - val consumer = source.consumer + val engine = FlowEngine.create(coroutineContext, clock) + val graph = engine.newGraph() + val sink = SimNetworkSink(graph, /*capacity*/ 100.0f) + val source = TestSource(graph) sink.connect(source) sink.disconnect() - assertFalse(sink.isConnected) - assertFalse(source.isConnected) - - verify { consumer.onStop(any(), any()) } - } - - private class Source(engine: FlowEngine) : SimNetworkPort() { - val consumer = spyk(FixedFlowSource(Double.POSITIVE_INFINITY, utilization = 0.8)) - - public override fun createConsumer(): FlowSource = consumer + yield() - override val provider: FlowConsumer = FlowSink(engine, 0.0) + assertAll( + { assertFalse(sink.isConnected) }, + { assertFalse(source.isConnected) }, + { assertEquals(0.0f, source.outlet.capacity) } + ) } } diff --git a/opendc-simulator/opendc-simulator-network/src/test/kotlin/org/opendc/simulator/network/SimNetworkSwitchVirtualTest.kt b/opendc-simulator/opendc-simulator-network/src/test/kotlin/org/opendc/simulator/network/SimNetworkSwitchVirtualTest.kt index ecf80818..1507c4a1 100644 --- a/opendc-simulator/opendc-simulator-network/src/test/kotlin/org/opendc/simulator/network/SimNetworkSwitchVirtualTest.kt +++ b/opendc-simulator/opendc-simulator-network/src/test/kotlin/org/opendc/simulator/network/SimNetworkSwitchVirtualTest.kt @@ -22,16 +22,14 @@ package org.opendc.simulator.network -import io.mockk.spyk import io.mockk.verify +import kotlinx.coroutines.yield +import org.junit.jupiter.api.Assertions.assertAll +import org.junit.jupiter.api.Assertions.assertEquals import org.junit.jupiter.api.Assertions.assertTrue import org.junit.jupiter.api.Test import org.junit.jupiter.api.assertThrows -import org.opendc.simulator.flow.FlowConsumer -import org.opendc.simulator.flow.FlowEngine -import org.opendc.simulator.flow.FlowSink -import org.opendc.simulator.flow.FlowSource -import org.opendc.simulator.flow.source.FixedFlowSource +import org.opendc.simulator.flow2.FlowEngine import org.opendc.simulator.kotlin.runSimulation /** @@ -40,27 +38,32 @@ import org.opendc.simulator.kotlin.runSimulation class SimNetworkSwitchVirtualTest { @Test fun testConnect() = runSimulation { - val engine = FlowEngine(coroutineContext, clock) - val sink = SimNetworkSink(engine, capacity = 100.0) - val source = spyk(Source(engine)) - val switch = SimNetworkSwitchVirtual(engine) - val consumer = source.consumer + val engine = FlowEngine.create(coroutineContext, clock) + val graph = engine.newGraph() + val sink = SimNetworkSink(graph, /*capacity*/ 100.0f) + val source = TestSource(graph) + val switch = SimNetworkSwitchVirtual(graph) switch.newPort().connect(sink) switch.newPort().connect(source) - assertTrue(sink.isConnected) - assertTrue(source.isConnected) + yield() - verify { source.createConsumer() } - verify { consumer.onStart(any(), any()) } + assertAll( + { assertTrue(sink.isConnected) }, + { assertTrue(source.isConnected) }, + { assertEquals(100.0f, source.outlet.capacity) } + ) + + verify { source.logic.onUpdate(any(), any()) } } @Test fun testConnectClosedPort() = runSimulation { - val engine = FlowEngine(coroutineContext, clock) - val sink = SimNetworkSink(engine, capacity = 100.0) - val switch = SimNetworkSwitchVirtual(engine) + val engine = FlowEngine.create(coroutineContext, clock) + val graph = engine.newGraph() + val sink = SimNetworkSink(graph, /*capacity*/ 100.0f) + val switch = SimNetworkSwitchVirtual(graph) val port = switch.newPort() port.close() @@ -69,12 +72,4 @@ class SimNetworkSwitchVirtualTest { port.connect(sink) } } - - private class Source(engine: FlowEngine) : SimNetworkPort() { - val consumer = spyk(FixedFlowSource(Double.POSITIVE_INFINITY, utilization = 0.8)) - - public override fun createConsumer(): FlowSource = consumer - - override val provider: FlowConsumer = FlowSink(engine, 0.0) - } } diff --git a/opendc-simulator/opendc-simulator-network/src/main/kotlin/org/opendc/simulator/network/SimNetworkSink.kt b/opendc-simulator/opendc-simulator-network/src/test/kotlin/org/opendc/simulator/network/TestSource.kt index 684b4a14..f69db7a2 100644 --- a/opendc-simulator/opendc-simulator-network/src/main/kotlin/org/opendc/simulator/network/SimNetworkSink.kt +++ b/opendc-simulator/opendc-simulator-network/src/test/kotlin/org/opendc/simulator/network/TestSource.kt @@ -1,5 +1,5 @@ /* - * Copyright (c) 2021 AtLarge Research + * Copyright (c) 2022 AtLarge Research * * Permission is hereby granted, free of charge, to any person obtaining a copy * of this software and associated documentation files (the "Software"), to deal @@ -22,26 +22,32 @@ package org.opendc.simulator.network -import org.opendc.simulator.flow.FlowConnection -import org.opendc.simulator.flow.FlowConsumer -import org.opendc.simulator.flow.FlowEngine -import org.opendc.simulator.flow.FlowSink -import org.opendc.simulator.flow.FlowSource +import io.mockk.spyk +import org.opendc.simulator.flow2.FlowGraph +import org.opendc.simulator.flow2.FlowStage +import org.opendc.simulator.flow2.FlowStageLogic +import org.opendc.simulator.flow2.InPort +import org.opendc.simulator.flow2.Inlet +import org.opendc.simulator.flow2.OutPort +import org.opendc.simulator.flow2.Outlet /** - * A network sink which discards all received traffic and does not generate any traffic itself. + * A [SimNetworkPort] that acts as a test source. */ -public class SimNetworkSink( - engine: FlowEngine, - public val capacity: Double -) : SimNetworkPort() { - override fun createConsumer(): FlowSource = object : FlowSource { - override fun onPull(conn: FlowConnection, now: Long): Long = Long.MAX_VALUE - - override fun toString(): String = "SimNetworkSink.Consumer" +class TestSource(graph: FlowGraph) : SimNetworkPort(), FlowStageLogic { + val logic = spyk(this) + private val stage = graph.newStage(logic) + + val outlet: OutPort = stage.getOutlet("out") + val inlet: InPort = stage.getInlet("in") + + init { + outlet.push(80.0f) } - override val provider: FlowConsumer = FlowSink(engine, capacity) + override fun onUpdate(ctx: FlowStage, now: Long): Long = Long.MAX_VALUE + + override fun getOutlet(): Outlet = outlet - override fun toString(): String = "SimNetworkSink[capacity=$capacity]" + override fun getInlet(): Inlet = inlet } diff --git a/opendc-simulator/opendc-simulator-power/src/main/java/org/opendc/simulator/power/SimPdu.java b/opendc-simulator/opendc-simulator-power/src/main/java/org/opendc/simulator/power/SimPdu.java new file mode 100644 index 00000000..8790a2d7 --- /dev/null +++ b/opendc-simulator/opendc-simulator-power/src/main/java/org/opendc/simulator/power/SimPdu.java @@ -0,0 +1,141 @@ +/* + * Copyright (c) 2022 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.jetbrains.annotations.NotNull; +import org.opendc.simulator.flow2.FlowGraph; +import org.opendc.simulator.flow2.Inlet; +import org.opendc.simulator.flow2.Outlet; +import org.opendc.simulator.flow2.mux.FlowMultiplexer; +import org.opendc.simulator.flow2.mux.MaxMinFlowMultiplexer; +import org.opendc.simulator.flow2.util.FlowTransform; +import org.opendc.simulator.flow2.util.FlowTransformer; + +/** + * A model of a Power Distribution Unit (PDU). + */ +public final class SimPdu extends SimPowerInlet { + /** + * The {@link FlowMultiplexer} that distributes the electricity over the PDU outlets. + */ + private final MaxMinFlowMultiplexer mux; + + /** + * A {@link FlowTransformer} that applies the power loss to the PDU's power inlet. + */ + private final FlowTransformer transformer; + + /** + * Construct a {@link SimPdu} instance. + * + * @param graph The underlying {@link FlowGraph} to which the PDU belongs. + * @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 SimPdu(FlowGraph graph, float idlePower, float lossCoefficient) { + this.mux = new MaxMinFlowMultiplexer(graph); + this.transformer = new FlowTransformer(graph, new FlowTransform() { + @Override + public float apply(float value) { + // See https://download.schneider-electric.com/files?p_Doc_Ref=SPD_NRAN-66CK3D_EN + return value * (lossCoefficient * value + 1) + idlePower; + } + + @Override + public float applyInverse(float value) { + float c = lossCoefficient; + if (c != 0.f) { + return (float) (1 + Math.sqrt(4 * value * c - 4 * idlePower * c + 1)) / (2 * c); + } else { + return value - idlePower; + } + } + }); + + graph.connect(mux.newOutput(), transformer.getInput()); + } + + /** + * Construct a {@link SimPdu} instance without any loss. + * + * @param graph The underlying {@link FlowGraph} to which the PDU belongs. + */ + public SimPdu(FlowGraph graph) { + this(graph, 0.f, 0.f); + } + + /** + * Create a new PDU outlet. + */ + public PowerOutlet newOutlet() { + return new PowerOutlet(mux); + } + + @NotNull + @Override + public Outlet getFlowOutlet() { + return transformer.getOutput(); + } + + @Override + public String toString() { + return "SimPdu"; + } + + /** + * A PDU outlet. + */ + public static final class PowerOutlet extends SimPowerOutlet implements AutoCloseable { + private final FlowMultiplexer mux; + private final Inlet inlet; + private boolean isClosed; + + private PowerOutlet(FlowMultiplexer mux) { + this.mux = mux; + this.inlet = mux.newInput(); + } + + /** + * Remove the outlet from the PDU. + */ + @Override + public void close() { + isClosed = true; + mux.releaseInput(inlet); + } + + @Override + public String toString() { + return "SimPdu.Outlet"; + } + + @NotNull + @Override + protected Inlet getFlowInlet() { + if (isClosed) { + throw new IllegalStateException("Outlet is closed"); + } + return inlet; + } + } +} 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/java/org/opendc/simulator/power/SimPowerInlet.java index de587b7f..a6e167c2 100644 --- a/opendc-simulator/opendc-simulator-power/src/main/kotlin/org/opendc/simulator/power/SimPowerInlet.kt +++ b/opendc-simulator/opendc-simulator-power/src/main/java/org/opendc/simulator/power/SimPowerInlet.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2021 AtLarge Research + * Copyright (c) 2022 AtLarge Research * * Permission is hereby granted, free of charge, to any person obtaining a copy * of this software and associated documentation files (the "Software"), to deal @@ -20,29 +20,34 @@ * SOFTWARE. */ -package org.opendc.simulator.power +package org.opendc.simulator.power; -import org.opendc.simulator.flow.FlowSource +import org.opendc.simulator.flow2.Outlet; /** * An abstract inlet that consumes electricity from a power outlet. */ public abstract class SimPowerInlet { + SimPowerOutlet outlet; + /** - * A flag to indicate that the inlet is currently connected to an outlet. + * Determine whether the inlet is connected to a {@link SimPowerOutlet}. + * + * @return <code>true</code> if the inlet is connected to an outlet, <code>false</code> otherwise. */ - public val isConnected: Boolean - get() = _outlet != null + public boolean isConnected() { + return outlet != null; + } /** - * The [SimPowerOutlet] to which the inlet is connected. + * Return the {@link SimPowerOutlet} to which the inlet is connected. */ - public val outlet: SimPowerOutlet? - get() = _outlet - internal var _outlet: SimPowerOutlet? = null + public SimPowerOutlet getOutlet() { + return outlet; + } /** - * Create a [FlowSource] which represents the consumption of electricity from the power outlet. + * Return the flow {@link Outlet} that models the consumption of a power inlet as flow output. */ - public abstract fun createSource(): FlowSource + protected abstract Outlet getFlowOutlet(); } diff --git a/opendc-simulator/opendc-simulator-power/src/main/java/org/opendc/simulator/power/SimPowerOutlet.java b/opendc-simulator/opendc-simulator-power/src/main/java/org/opendc/simulator/power/SimPowerOutlet.java new file mode 100644 index 00000000..e33d35d0 --- /dev/null +++ b/opendc-simulator/opendc-simulator-power/src/main/java/org/opendc/simulator/power/SimPowerOutlet.java @@ -0,0 +1,91 @@ +/* + * Copyright (c) 2022 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.flow2.Inlet; +import org.opendc.simulator.flow2.Outlet; + +/** + * An abstract outlet that provides a source of electricity for datacenter components. + */ +public abstract class SimPowerOutlet { + private SimPowerInlet inlet; + + /** + * Determine whether the outlet is connected to a {@link SimPowerInlet}. + * + * @return <code>true</code> if the outlet is connected to an inlet, <code>false</code> otherwise. + */ + public boolean isConnected() { + return inlet != null; + } + + /** + * Return the {@link SimPowerInlet} to which the outlet is connected. + */ + public SimPowerInlet getInlet() { + return inlet; + } + + /** + * Connect the specified power [inlet] to this outlet. + * + * @param inlet The inlet to connect to the outlet. + */ + public void connect(SimPowerInlet inlet) { + if (isConnected()) { + throw new IllegalStateException("Outlet already connected"); + } + if (inlet.isConnected()) { + throw new IllegalStateException("Inlet already connected"); + } + + this.inlet = inlet; + this.inlet.outlet = this; + + final Inlet flowInlet = getFlowInlet(); + final Outlet flowOutlet = inlet.getFlowOutlet(); + + flowInlet.getGraph().connect(flowOutlet, flowInlet); + } + + /** + * Disconnect the connected power outlet from this inlet + */ + public void disconnect() { + SimPowerInlet inlet = this.inlet; + if (inlet != null) { + this.inlet = null; + assert inlet.outlet == this : "Inlet state incorrect"; + inlet.outlet = null; + + final Inlet flowInlet = getFlowInlet(); + flowInlet.getGraph().disconnect(flowInlet); + } + } + + /** + * Return the flow {@link Inlet} that models the consumption of a power outlet as flow input. + */ + protected abstract Inlet getFlowInlet(); +} diff --git a/opendc-simulator/opendc-simulator-power/src/main/java/org/opendc/simulator/power/SimPowerSource.java b/opendc-simulator/opendc-simulator-power/src/main/java/org/opendc/simulator/power/SimPowerSource.java new file mode 100644 index 00000000..a2d62c48 --- /dev/null +++ b/opendc-simulator/opendc-simulator-power/src/main/java/org/opendc/simulator/power/SimPowerSource.java @@ -0,0 +1,71 @@ +/* + * Copyright (c) 2022 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.flow2.FlowGraph; +import org.opendc.simulator.flow2.Inlet; +import org.opendc.simulator.flow2.sink.SimpleFlowSink; + +/** + * A {@link SimPowerOutlet} that represents a source of electricity with a maximum capacity. + */ +public final class SimPowerSource extends SimPowerOutlet { + /** + * The resource source that drives this power source. + */ + private final SimpleFlowSink sink; + + /** + * Construct a {@link SimPowerSource} instance. + * + * @param graph The underlying {@link FlowGraph} to which the power source belongs. + * @param capacity The maximum amount of power provided by the source. + */ + public SimPowerSource(FlowGraph graph, float capacity) { + this.sink = new SimpleFlowSink(graph, capacity); + } + + /** + * Return the capacity of the power source. + */ + public float getCapacity() { + return sink.getCapacity(); + } + + /** + * Return the power draw at this instant. + */ + public float getPowerDraw() { + return sink.getRate(); + } + + @Override + protected Inlet getFlowInlet() { + return sink.getInput(); + } + + @Override + public String toString() { + return "SimPowerSource"; + } +} diff --git a/opendc-simulator/opendc-simulator-power/src/main/java/org/opendc/simulator/power/SimUps.java b/opendc-simulator/opendc-simulator-power/src/main/java/org/opendc/simulator/power/SimUps.java new file mode 100644 index 00000000..df7508d9 --- /dev/null +++ b/opendc-simulator/opendc-simulator-power/src/main/java/org/opendc/simulator/power/SimUps.java @@ -0,0 +1,137 @@ +/* + * Copyright (c) 2022 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.jetbrains.annotations.NotNull; +import org.opendc.simulator.flow2.FlowGraph; +import org.opendc.simulator.flow2.Inlet; +import org.opendc.simulator.flow2.Outlet; +import org.opendc.simulator.flow2.mux.FlowMultiplexer; +import org.opendc.simulator.flow2.mux.MaxMinFlowMultiplexer; +import org.opendc.simulator.flow2.util.FlowTransform; +import org.opendc.simulator.flow2.util.FlowTransformer; + +/** + * A model of an Uninterruptible Power Supply (UPS). + * <p> + * This model aggregates multiple power sources into a single source in order to ensure that power is always available. + */ +public final class SimUps extends SimPowerOutlet { + /** + * The {@link FlowMultiplexer} that distributes the electricity over the PDU outlets. + */ + private final MaxMinFlowMultiplexer mux; + + /** + * A {@link FlowTransformer} that applies the power loss to the PDU's power inlet. + */ + private final FlowTransformer transformer; + + /** + * Construct a {@link SimUps} instance. + * + * @param graph The underlying {@link FlowGraph} to which the UPS belongs. + * @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 SimUps(FlowGraph graph, float idlePower, float lossCoefficient) { + this.mux = new MaxMinFlowMultiplexer(graph); + this.transformer = new FlowTransformer(graph, new FlowTransform() { + @Override + public float apply(float value) { + // See https://download.schneider-electric.com/files?p_Doc_Ref=SPD_NRAN-66CK3D_EN + return value * (lossCoefficient + 1) + idlePower; + } + + @Override + public float applyInverse(float value) { + return (value - idlePower) / (lossCoefficient + 1); + } + }); + + graph.connect(transformer.getOutput(), mux.newInput()); + } + + /** + * Construct a {@link SimUps} instance without any loss. + * + * @param graph The underlying {@link FlowGraph} to which the UPS belongs. + */ + public SimUps(FlowGraph graph) { + this(graph, 0.f, 0.f); + } + + /** + * Create a new UPS inlet. + */ + public PowerInlet newInlet() { + return new PowerInlet(mux); + } + + @Override + protected Inlet getFlowInlet() { + return transformer.getInput(); + } + + @Override + public String toString() { + return "SimUps"; + } + + /** + * A UPS inlet. + */ + public static final class PowerInlet extends SimPowerInlet implements AutoCloseable { + private final FlowMultiplexer mux; + private final Outlet outlet; + private boolean isClosed; + + private PowerInlet(FlowMultiplexer mux) { + this.mux = mux; + this.outlet = mux.newOutput(); + } + + /** + * Remove the inlet from the PDU. + */ + @Override + public void close() { + isClosed = true; + mux.releaseOutput(outlet); + } + + @Override + public String toString() { + return "SimPdu.Inlet"; + } + + @NotNull + @Override + protected Outlet getFlowOutlet() { + if (isClosed) { + throw new IllegalStateException("Inlet is closed"); + } + return outlet; + } + } +} 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 deleted file mode 100644 index c4076310..00000000 --- a/opendc-simulator/opendc-simulator-power/src/main/kotlin/org/opendc/simulator/power/SimPdu.kt +++ /dev/null @@ -1,95 +0,0 @@ -/* - * 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.flow.FlowConsumer -import org.opendc.simulator.flow.FlowEngine -import org.opendc.simulator.flow.FlowMapper -import org.opendc.simulator.flow.FlowSource -import org.opendc.simulator.flow.mux.FlowMultiplexer -import org.opendc.simulator.flow.mux.MaxMinFlowMultiplexer - -/** - * A model of a Power Distribution Unit (PDU). - * - * @param engine The underlying [FlowEngine] 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( - engine: FlowEngine, - private val idlePower: Double = 0.0, - private val lossCoefficient: Double = 0.0 -) : SimPowerInlet() { - /** - * The [FlowMultiplexer] that distributes the electricity over the PDU outlets. - */ - private val mux = MaxMinFlowMultiplexer(engine) - - /** - * The [FlowForwarder] that represents the input of the PDU. - */ - private val output = mux.newOutput() - - /** - * Create a new PDU outlet. - */ - public fun newOutlet(): Outlet = Outlet(mux, mux.newInput()) - - override fun createSource(): FlowSource = FlowMapper(output) { _, rate -> - val loss = computePowerLoss(rate) - rate + loss - } - - 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. - */ - public class Outlet(private val switch: FlowMultiplexer, private val provider: FlowConsumer) : SimPowerOutlet(), AutoCloseable { - override fun onConnect(inlet: SimPowerInlet) { - provider.startConsumer(inlet.createSource()) - } - - override fun onDisconnect(inlet: SimPowerInlet) { - provider.cancel() - } - - /** - * Remove the outlet from the PDU. - */ - override fun close() { - switch.removeInput(provider) - } - - override fun toString(): String = "SimPdu.Outlet" - } -} 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 deleted file mode 100644 index 72f52acc..00000000 --- a/opendc-simulator/opendc-simulator-power/src/main/kotlin/org/opendc/simulator/power/SimPowerOutlet.kt +++ /dev/null @@ -1,80 +0,0 @@ -/* - * 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/SimUps.kt b/opendc-simulator/opendc-simulator-power/src/main/kotlin/org/opendc/simulator/power/SimUps.kt deleted file mode 100644 index 0431d3cf..00000000 --- a/opendc-simulator/opendc-simulator-power/src/main/kotlin/org/opendc/simulator/power/SimUps.kt +++ /dev/null @@ -1,101 +0,0 @@ -/* - * 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.flow.FlowEngine -import org.opendc.simulator.flow.FlowForwarder -import org.opendc.simulator.flow.FlowMapper -import org.opendc.simulator.flow.FlowSource -import org.opendc.simulator.flow.mux.MaxMinFlowMultiplexer - -/** - * 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 engine The underlying [FlowEngine] 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( - private val engine: FlowEngine, - private val idlePower: Double = 0.0, - private val lossCoefficient: Double = 0.0 -) : SimPowerOutlet() { - /** - * The resource aggregator used to combine the input sources. - */ - private val mux = MaxMinFlowMultiplexer(engine) - - /** - * The [FlowConsumer] that represents the output of the UPS. - */ - private val provider = mux.newInput() - - /** - * Create a new UPS outlet. - */ - public fun newInlet(): SimPowerInlet { - val forward = FlowForwarder(engine, isCoupled = true) - forward.startConsumer(mux.newOutput()) - return Inlet(forward) - } - - override fun onConnect(inlet: SimPowerInlet) { - val source = inlet.createSource() - val mapper = FlowMapper(source) { _, rate -> - val loss = computePowerLoss(rate) - rate + loss - } - - provider.startConsumer(mapper) - } - - override fun onDisconnect(inlet: SimPowerInlet) { - provider.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: FlowForwarder) : SimPowerInlet(), AutoCloseable { - override fun createSource(): FlowSource = 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 29c50d3f..6adb0548 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 @@ -22,14 +22,11 @@ package org.opendc.simulator.power -import io.mockk.spyk -import io.mockk.verify +import kotlinx.coroutines.yield import org.junit.jupiter.api.Assertions.assertEquals import org.junit.jupiter.api.Test import org.junit.jupiter.api.assertThrows -import org.opendc.simulator.flow.FlowEngine -import org.opendc.simulator.flow.FlowSource -import org.opendc.simulator.flow.source.FixedFlowSource +import org.opendc.simulator.flow2.FlowEngine import org.opendc.simulator.kotlin.runSimulation /** @@ -38,82 +35,93 @@ import org.opendc.simulator.kotlin.runSimulation internal class SimPduTest { @Test fun testZeroOutlets() = runSimulation { - val engine = FlowEngine(coroutineContext, clock) - val source = SimPowerSource(engine, capacity = 100.0) - val pdu = SimPdu(engine) + val engine = FlowEngine.create(coroutineContext, clock) + val graph = engine.newGraph() + val source = SimPowerSource(graph, /*capacity*/ 100.0f) + val pdu = SimPdu(graph) source.connect(pdu) - assertEquals(0.0, source.powerDraw) + yield() + + assertEquals(0.0f, source.powerDraw) } @Test fun testSingleOutlet() = runSimulation { - val engine = FlowEngine(coroutineContext, clock) - val source = SimPowerSource(engine, capacity = 100.0) - val pdu = SimPdu(engine) + val engine = FlowEngine.create(coroutineContext, clock) + val graph = engine.newGraph() + val source = SimPowerSource(graph, /*capacity*/ 100.0f) + val pdu = SimPdu(graph) source.connect(pdu) - pdu.newOutlet().connect(SimpleInlet()) + pdu.newOutlet().connect(TestInlet(graph)) + + yield() - assertEquals(50.0, source.powerDraw) + assertEquals(100.0f, source.powerDraw) } @Test fun testDoubleOutlet() = runSimulation { - val engine = FlowEngine(coroutineContext, clock) - val source = SimPowerSource(engine, capacity = 100.0) - val pdu = SimPdu(engine) + val engine = FlowEngine.create(coroutineContext, clock) + val graph = engine.newGraph() + val source = SimPowerSource(graph, /*capacity*/ 200.0f) + val pdu = SimPdu(graph) source.connect(pdu) - pdu.newOutlet().connect(SimpleInlet()) - pdu.newOutlet().connect(SimpleInlet()) + pdu.newOutlet().connect(TestInlet(graph)) + pdu.newOutlet().connect(TestInlet(graph)) + + yield() - assertEquals(100.0, source.powerDraw) + assertEquals(200.0f, source.powerDraw) } @Test fun testDisconnect() = runSimulation { - val engine = FlowEngine(coroutineContext, clock) - val source = SimPowerSource(engine, capacity = 100.0) - val pdu = SimPdu(engine) + val engine = FlowEngine.create(coroutineContext, clock) + val graph = engine.newGraph() + val source = SimPowerSource(graph, /*capacity*/ 300.0f) + val pdu = SimPdu(graph) source.connect(pdu) - val consumer = spyk(FixedFlowSource(100.0, utilization = 1.0)) - val inlet = object : SimPowerInlet() { - override fun createSource(): FlowSource = consumer - } val outlet = pdu.newOutlet() - outlet.connect(inlet) + outlet.connect(TestInlet(graph)) outlet.disconnect() - verify { consumer.onStop(any(), any()) } + yield() + + assertEquals(0.0f, source.powerDraw) } @Test fun testLoss() = runSimulation { - val engine = FlowEngine(coroutineContext, clock) - val source = SimPowerSource(engine, capacity = 100.0) + val engine = FlowEngine.create(coroutineContext, clock) + val graph = engine.newGraph() + val source = SimPowerSource(graph, /*capacity*/ 500.0f) // https://download.schneider-electric.com/files?p_Doc_Ref=SPD_NRAN-66CK3D_EN - val pdu = SimPdu(engine, idlePower = 1.5, lossCoefficient = 0.015) + val pdu = SimPdu(graph, /*idlePower*/ 1.5f, /*lossCoefficient*/ 0.015f) source.connect(pdu) - pdu.newOutlet().connect(SimpleInlet()) - assertEquals(89.0, source.powerDraw, 0.01) + pdu.newOutlet().connect(TestInlet(graph)) + + yield() + + assertEquals(251.5f, source.powerDraw, 0.01f) } @Test fun testOutletClose() = runSimulation { - val engine = FlowEngine(coroutineContext, clock) - val source = SimPowerSource(engine, capacity = 100.0) - val pdu = SimPdu(engine) + val engine = FlowEngine.create(coroutineContext, clock) + val graph = engine.newGraph() + val source = SimPowerSource(graph, /*capacity*/ 100.0f) + val pdu = SimPdu(graph) source.connect(pdu) val outlet = pdu.newOutlet() outlet.close() + yield() + assertThrows<IllegalStateException> { - outlet.connect(SimpleInlet()) + outlet.connect(TestInlet(graph)) } } - - class SimpleInlet : SimPowerInlet() { - override fun createSource(): FlowSource = FixedFlowSource(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 index 963ba710..03b8182c 100644 --- 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 @@ -24,8 +24,8 @@ package org.opendc.simulator.power import io.mockk.every import io.mockk.mockk -import io.mockk.spyk -import io.mockk.verify +import kotlinx.coroutines.yield +import org.junit.jupiter.api.Assertions.assertAll import org.junit.jupiter.api.Assertions.assertEquals import org.junit.jupiter.api.Assertions.assertFalse import org.junit.jupiter.api.Assertions.assertNull @@ -33,9 +33,7 @@ import org.junit.jupiter.api.Assertions.assertTrue import org.junit.jupiter.api.Test import org.junit.jupiter.api.assertDoesNotThrow import org.junit.jupiter.api.assertThrows -import org.opendc.simulator.flow.FlowEngine -import org.opendc.simulator.flow.FlowSource -import org.opendc.simulator.flow.source.FixedFlowSource +import org.opendc.simulator.flow2.FlowEngine import org.opendc.simulator.kotlin.runSimulation /** @@ -44,18 +42,24 @@ import org.opendc.simulator.kotlin.runSimulation internal class SimPowerSourceTest { @Test fun testInitialState() = runSimulation { - val engine = FlowEngine(coroutineContext, clock) - val source = SimPowerSource(engine, capacity = 100.0) + val engine = FlowEngine.create(coroutineContext, clock) + val graph = engine.newGraph() + val source = SimPowerSource(graph, /*capacity*/ 100.0f) - assertFalse(source.isConnected) - assertNull(source.inlet) - assertEquals(100.0, source.capacity) + yield() + + assertAll( + { assertFalse(source.isConnected) }, + { assertNull(source.inlet) }, + { assertEquals(100.0f, source.capacity) } + ) } @Test fun testDisconnectIdempotent() = runSimulation { - val engine = FlowEngine(coroutineContext, clock) - val source = SimPowerSource(engine, capacity = 100.0) + val engine = FlowEngine.create(coroutineContext, clock) + val graph = engine.newGraph() + val source = SimPowerSource(graph, /*capacity*/ 100.0f) assertDoesNotThrow { source.disconnect() } assertFalse(source.isConnected) @@ -63,44 +67,51 @@ internal class SimPowerSourceTest { @Test fun testConnect() = runSimulation { - val engine = FlowEngine(coroutineContext, clock) - val source = SimPowerSource(engine, capacity = 100.0) - val inlet = SimpleInlet() + val engine = FlowEngine.create(coroutineContext, clock) + val graph = engine.newGraph() + val source = SimPowerSource(graph, /*capacity*/ 100.0f) + val inlet = TestInlet(graph) source.connect(inlet) - assertTrue(source.isConnected) - assertEquals(inlet, source.inlet) - assertTrue(inlet.isConnected) - assertEquals(source, inlet.outlet) - assertEquals(100.0, source.powerDraw) + yield() + + assertAll( + { assertTrue(source.isConnected) }, + { assertEquals(inlet, source.inlet) }, + { assertTrue(inlet.isConnected) }, + { assertEquals(source, inlet.outlet) }, + { assertEquals(100.0f, source.powerDraw) } + ) } @Test fun testDisconnect() = runSimulation { - val engine = FlowEngine(coroutineContext, clock) - val source = SimPowerSource(engine, capacity = 100.0) - val consumer = spyk(FixedFlowSource(100.0, utilization = 1.0)) - val inlet = object : SimPowerInlet() { - override fun createSource(): FlowSource = consumer - } + val engine = FlowEngine.create(coroutineContext, clock) + val graph = engine.newGraph() + val source = SimPowerSource(graph, /*capacity*/ 100.0f) + val inlet = TestInlet(graph) source.connect(inlet) source.disconnect() - verify { consumer.onStop(any(), any()) } + yield() + + assertEquals(0.0f, inlet.flowOutlet.capacity) } @Test fun testDisconnectAssertion() = runSimulation { - val engine = FlowEngine(coroutineContext, clock) - val source = SimPowerSource(engine, capacity = 100.0) + val engine = FlowEngine.create(coroutineContext, clock) + val graph = engine.newGraph() + val source = SimPowerSource(graph, /*capacity*/ 100.0f) + val inlet = mockk<SimPowerInlet>(relaxUnitFun = true) every { inlet.isConnected } returns false - every { inlet._outlet } returns null - every { inlet.createSource() } returns FixedFlowSource(100.0, utilization = 1.0) + every { inlet.flowOutlet } returns TestInlet(graph).flowOutlet source.connect(inlet) + inlet.outlet = null assertThrows<AssertionError> { source.disconnect() @@ -109,13 +120,14 @@ internal class SimPowerSourceTest { @Test fun testOutletAlreadyConnected() = runSimulation { - val engine = FlowEngine(coroutineContext, clock) - val source = SimPowerSource(engine, capacity = 100.0) - val inlet = SimpleInlet() + val engine = FlowEngine.create(coroutineContext, clock) + val graph = engine.newGraph() + val source = SimPowerSource(graph, /*capacity*/ 100.0f) + val inlet = TestInlet(graph) source.connect(inlet) assertThrows<IllegalStateException> { - source.connect(SimpleInlet()) + source.connect(TestInlet(graph)) } assertEquals(inlet, source.inlet) @@ -123,8 +135,9 @@ internal class SimPowerSourceTest { @Test fun testInletAlreadyConnected() = runSimulation { - val engine = FlowEngine(coroutineContext, clock) - val source = SimPowerSource(engine, capacity = 100.0) + val engine = FlowEngine.create(coroutineContext, clock) + val graph = engine.newGraph() + val source = SimPowerSource(graph, /*capacity*/ 100.0f) val inlet = mockk<SimPowerInlet>(relaxUnitFun = true) every { inlet.isConnected } returns true @@ -132,8 +145,4 @@ internal class SimPowerSourceTest { source.connect(inlet) } } - - class SimpleInlet : SimPowerInlet() { - override fun createSource(): FlowSource = FixedFlowSource(100.0, utilization = 1.0) - } } 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 index 2b2921d7..0dd7bb05 100644 --- 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 @@ -22,14 +22,11 @@ package org.opendc.simulator.power -import io.mockk.spyk -import io.mockk.verify +import kotlinx.coroutines.yield import org.junit.jupiter.api.Assertions.assertAll import org.junit.jupiter.api.Assertions.assertEquals import org.junit.jupiter.api.Test -import org.opendc.simulator.flow.FlowEngine -import org.opendc.simulator.flow.FlowSource -import org.opendc.simulator.flow.source.FixedFlowSource +import org.opendc.simulator.flow2.FlowEngine import org.opendc.simulator.kotlin.runSimulation /** @@ -38,64 +35,70 @@ import org.opendc.simulator.kotlin.runSimulation internal class SimUpsTest { @Test fun testSingleInlet() = runSimulation { - val engine = FlowEngine(coroutineContext, clock) - val source = SimPowerSource(engine, capacity = 100.0) - val ups = SimUps(engine) + val engine = FlowEngine.create(coroutineContext, clock) + val graph = engine.newGraph() + val source = SimPowerSource(graph, /*capacity*/ 200.0f) + val ups = SimUps(graph) source.connect(ups.newInlet()) - ups.connect(SimpleInlet()) + ups.connect(TestInlet(graph)) - assertEquals(50.0, source.powerDraw) + yield() + + assertEquals(100.0f, source.powerDraw) } @Test fun testDoubleInlet() = runSimulation { - val engine = FlowEngine(coroutineContext, clock) - val source1 = SimPowerSource(engine, capacity = 100.0) - val source2 = SimPowerSource(engine, capacity = 100.0) - val ups = SimUps(engine) + val engine = FlowEngine.create(coroutineContext, clock) + val graph = engine.newGraph() + val source1 = SimPowerSource(graph, /*capacity*/ 200.0f) + val source2 = SimPowerSource(graph, /*capacity*/ 200.0f) + val ups = SimUps(graph) source1.connect(ups.newInlet()) source2.connect(ups.newInlet()) - ups.connect(SimpleInlet()) + ups.connect(TestInlet(graph)) + + yield() assertAll( - { assertEquals(50.0, source1.powerDraw) }, - { assertEquals(50.0, source2.powerDraw) } + { assertEquals(50.0f, source1.powerDraw) }, + { assertEquals(50.0f, source2.powerDraw) } ) } @Test fun testLoss() = runSimulation { - val engine = FlowEngine(coroutineContext, clock) - val source = SimPowerSource(engine, capacity = 100.0) + val engine = FlowEngine.create(coroutineContext, clock) + val graph = engine.newGraph() + val source = SimPowerSource(graph, /*capacity*/ 500.0f) // https://download.schneider-electric.com/files?p_Doc_Ref=SPD_NRAN-66CK3D_EN - val ups = SimUps(engine, idlePower = 4.0, lossCoefficient = 0.05) + val ups = SimUps(graph, /*idlePower*/ 4.0f, /*lossCoefficient*/ 0.05f) source.connect(ups.newInlet()) - ups.connect(SimpleInlet()) + ups.connect(TestInlet(graph)) + + yield() - assertEquals(56.5, source.powerDraw) + assertEquals(109.0f, source.powerDraw, 0.01f) } @Test fun testDisconnect() = runSimulation { - val engine = FlowEngine(coroutineContext, clock) - val source1 = SimPowerSource(engine, capacity = 100.0) - val source2 = SimPowerSource(engine, capacity = 100.0) - val ups = SimUps(engine) + val engine = FlowEngine.create(coroutineContext, clock) + val graph = engine.newGraph() + val source1 = SimPowerSource(graph, /*capacity*/ 200.0f) + val source2 = SimPowerSource(graph, /*capacity*/ 200.0f) + val ups = SimUps(graph) source1.connect(ups.newInlet()) source2.connect(ups.newInlet()) - val consumer = spyk(FixedFlowSource(100.0, utilization = 1.0)) - val inlet = object : SimPowerInlet() { - override fun createSource(): FlowSource = consumer - } + + val inlet = TestInlet(graph) ups.connect(inlet) ups.disconnect() - verify { consumer.onStop(any(), any()) } - } + yield() - class SimpleInlet : SimPowerInlet() { - override fun createSource(): FlowSource = FixedFlowSource(100.0, utilization = 0.5) + assertEquals(0.0f, inlet.flowOutlet.capacity) } } diff --git a/opendc-simulator/opendc-simulator-compute/src/main/kotlin/org/opendc/simulator/compute/kernel/cpufreq/PowerSaveScalingGovernor.kt b/opendc-simulator/opendc-simulator-power/src/test/kotlin/org/opendc/simulator/power/TestInlet.kt index 32c0703a..7ba12ed9 100644 --- a/opendc-simulator/opendc-simulator-compute/src/main/kotlin/org/opendc/simulator/compute/kernel/cpufreq/PowerSaveScalingGovernor.kt +++ b/opendc-simulator/opendc-simulator-power/src/test/kotlin/org/opendc/simulator/power/TestInlet.kt @@ -1,5 +1,5 @@ /* - * Copyright (c) 2021 AtLarge Research + * Copyright (c) 2022 AtLarge Research * * Permission is hereby granted, free of charge, to any person obtaining a copy * of this software and associated documentation files (the "Software"), to deal @@ -20,17 +20,29 @@ * SOFTWARE. */ -package org.opendc.simulator.compute.kernel.cpufreq +package org.opendc.simulator.power + +import io.mockk.spyk +import org.opendc.simulator.flow2.FlowGraph +import org.opendc.simulator.flow2.FlowStage +import org.opendc.simulator.flow2.FlowStageLogic +import org.opendc.simulator.flow2.Outlet /** - * A CPUFreq [ScalingGovernor] that causes the lowest possible frequency to be requested from the resource. + * A test inlet. */ -public class PowerSaveScalingGovernor : ScalingGovernor { - override fun createLogic(policy: ScalingPolicy): ScalingGovernor.Logic = object : ScalingGovernor.Logic { - override fun onStart() { - policy.target = policy.min - } +class TestInlet(graph: FlowGraph) : SimPowerInlet(), FlowStageLogic { + val logic = spyk(this) + private val stage = graph.newStage(logic) + val flowOutlet = stage.getOutlet("out") + + init { + flowOutlet.push(100.0f) } - override fun toString(): String = "PowerSaveScalingGovernor" + override fun onUpdate(ctx: FlowStage, now: Long): Long = Long.MAX_VALUE + + override fun getFlowOutlet(): Outlet { + return flowOutlet + } } |
