From 44215bd668c5fa3efe2f57fc577824478b00af57 Mon Sep 17 00:00:00 2001 From: Fabian Mastenbroek Date: Thu, 1 Sep 2022 14:38:34 +0200 Subject: refactor(sim/compute): Re-implement using flow2 This change re-implements the OpenDC compute simulator framework using the new flow2 framework for modelling multi-edge flow networks. The re-implementation is written in Java and focusses on performance and clean API surface. --- .../simulator/compute/SimAbstractMachine.java | 327 ++++++++ .../simulator/compute/SimBareMetalMachine.java | 298 +++++++ .../org/opendc/simulator/compute/SimMachine.java | 59 ++ .../simulator/compute/SimMachineContext.java | 74 ++ .../org/opendc/simulator/compute/SimMemory.java | 42 + .../simulator/compute/SimNetworkInterface.java | 51 ++ .../simulator/compute/SimProcessingUnit.java | 62 ++ .../java/org/opendc/simulator/compute/SimPsu.java | 71 ++ .../opendc/simulator/compute/SimPsuFactories.java | 214 +++++ .../opendc/simulator/compute/SimPsuFactory.java | 38 + .../simulator/compute/SimStorageInterface.java | 50 ++ .../compute/device/SimNetworkAdapter.java | 36 + .../simulator/compute/device/SimPeripheral.java | 33 + .../simulator/compute/kernel/SimHypervisor.java | 911 +++++++++++++++++++++ .../compute/kernel/SimHypervisorCounters.java | 53 ++ .../compute/kernel/SimVirtualMachine.java | 50 ++ .../compute/kernel/cpufreq/ScalingGovernor.java | 46 ++ .../kernel/cpufreq/ScalingGovernorFactory.java | 33 + .../compute/kernel/cpufreq/ScalingGovernors.java | 190 +++++ .../compute/kernel/cpufreq/ScalingPolicy.java | 56 ++ .../kernel/interference/VmInterferenceDomain.java | 136 +++ .../kernel/interference/VmInterferenceMember.java | 177 ++++ .../kernel/interference/VmInterferenceModel.java | 185 +++++ .../kernel/interference/VmInterferenceProfile.java | 60 ++ .../simulator/compute/model/MachineModel.java | 149 ++++ .../opendc/simulator/compute/model/MemoryUnit.java | 100 +++ .../simulator/compute/model/NetworkAdapter.java | 88 ++ .../simulator/compute/model/ProcessingNode.java | 100 +++ .../simulator/compute/model/ProcessingUnit.java | 86 ++ .../simulator/compute/model/StorageDevice.java | 112 +++ .../simulator/compute/power/CpuPowerModel.java | 38 + .../simulator/compute/power/CpuPowerModels.java | 330 ++++++++ .../compute/workload/SimFlopsWorkload.java | 141 ++++ .../compute/workload/SimRuntimeWorkload.java | 131 +++ .../simulator/compute/workload/SimTrace.java | 303 +++++++ .../compute/workload/SimTraceFragment.java | 94 +++ .../simulator/compute/workload/SimWorkload.java | 48 ++ .../compute/workload/SimWorkloadLifecycle.java | 60 ++ 38 files changed, 5032 insertions(+) create mode 100644 opendc-simulator/opendc-simulator-compute/src/main/java/org/opendc/simulator/compute/SimAbstractMachine.java create mode 100644 opendc-simulator/opendc-simulator-compute/src/main/java/org/opendc/simulator/compute/SimBareMetalMachine.java create mode 100644 opendc-simulator/opendc-simulator-compute/src/main/java/org/opendc/simulator/compute/SimMachine.java create mode 100644 opendc-simulator/opendc-simulator-compute/src/main/java/org/opendc/simulator/compute/SimMachineContext.java create mode 100644 opendc-simulator/opendc-simulator-compute/src/main/java/org/opendc/simulator/compute/SimMemory.java create mode 100644 opendc-simulator/opendc-simulator-compute/src/main/java/org/opendc/simulator/compute/SimNetworkInterface.java create mode 100644 opendc-simulator/opendc-simulator-compute/src/main/java/org/opendc/simulator/compute/SimProcessingUnit.java create mode 100644 opendc-simulator/opendc-simulator-compute/src/main/java/org/opendc/simulator/compute/SimPsu.java create mode 100644 opendc-simulator/opendc-simulator-compute/src/main/java/org/opendc/simulator/compute/SimPsuFactories.java create mode 100644 opendc-simulator/opendc-simulator-compute/src/main/java/org/opendc/simulator/compute/SimPsuFactory.java create mode 100644 opendc-simulator/opendc-simulator-compute/src/main/java/org/opendc/simulator/compute/SimStorageInterface.java create mode 100644 opendc-simulator/opendc-simulator-compute/src/main/java/org/opendc/simulator/compute/device/SimNetworkAdapter.java create mode 100644 opendc-simulator/opendc-simulator-compute/src/main/java/org/opendc/simulator/compute/device/SimPeripheral.java create mode 100644 opendc-simulator/opendc-simulator-compute/src/main/java/org/opendc/simulator/compute/kernel/SimHypervisor.java create mode 100644 opendc-simulator/opendc-simulator-compute/src/main/java/org/opendc/simulator/compute/kernel/SimHypervisorCounters.java create mode 100644 opendc-simulator/opendc-simulator-compute/src/main/java/org/opendc/simulator/compute/kernel/SimVirtualMachine.java create mode 100644 opendc-simulator/opendc-simulator-compute/src/main/java/org/opendc/simulator/compute/kernel/cpufreq/ScalingGovernor.java create mode 100644 opendc-simulator/opendc-simulator-compute/src/main/java/org/opendc/simulator/compute/kernel/cpufreq/ScalingGovernorFactory.java create mode 100644 opendc-simulator/opendc-simulator-compute/src/main/java/org/opendc/simulator/compute/kernel/cpufreq/ScalingGovernors.java create mode 100644 opendc-simulator/opendc-simulator-compute/src/main/java/org/opendc/simulator/compute/kernel/cpufreq/ScalingPolicy.java create mode 100644 opendc-simulator/opendc-simulator-compute/src/main/java/org/opendc/simulator/compute/kernel/interference/VmInterferenceDomain.java create mode 100644 opendc-simulator/opendc-simulator-compute/src/main/java/org/opendc/simulator/compute/kernel/interference/VmInterferenceMember.java create mode 100644 opendc-simulator/opendc-simulator-compute/src/main/java/org/opendc/simulator/compute/kernel/interference/VmInterferenceModel.java create mode 100644 opendc-simulator/opendc-simulator-compute/src/main/java/org/opendc/simulator/compute/kernel/interference/VmInterferenceProfile.java create mode 100644 opendc-simulator/opendc-simulator-compute/src/main/java/org/opendc/simulator/compute/model/MachineModel.java create mode 100644 opendc-simulator/opendc-simulator-compute/src/main/java/org/opendc/simulator/compute/model/MemoryUnit.java create mode 100644 opendc-simulator/opendc-simulator-compute/src/main/java/org/opendc/simulator/compute/model/NetworkAdapter.java create mode 100644 opendc-simulator/opendc-simulator-compute/src/main/java/org/opendc/simulator/compute/model/ProcessingNode.java create mode 100644 opendc-simulator/opendc-simulator-compute/src/main/java/org/opendc/simulator/compute/model/ProcessingUnit.java create mode 100644 opendc-simulator/opendc-simulator-compute/src/main/java/org/opendc/simulator/compute/model/StorageDevice.java create mode 100644 opendc-simulator/opendc-simulator-compute/src/main/java/org/opendc/simulator/compute/power/CpuPowerModel.java create mode 100644 opendc-simulator/opendc-simulator-compute/src/main/java/org/opendc/simulator/compute/power/CpuPowerModels.java create mode 100644 opendc-simulator/opendc-simulator-compute/src/main/java/org/opendc/simulator/compute/workload/SimFlopsWorkload.java create mode 100644 opendc-simulator/opendc-simulator-compute/src/main/java/org/opendc/simulator/compute/workload/SimRuntimeWorkload.java create mode 100644 opendc-simulator/opendc-simulator-compute/src/main/java/org/opendc/simulator/compute/workload/SimTrace.java create mode 100644 opendc-simulator/opendc-simulator-compute/src/main/java/org/opendc/simulator/compute/workload/SimTraceFragment.java create mode 100644 opendc-simulator/opendc-simulator-compute/src/main/java/org/opendc/simulator/compute/workload/SimWorkload.java create mode 100644 opendc-simulator/opendc-simulator-compute/src/main/java/org/opendc/simulator/compute/workload/SimWorkloadLifecycle.java (limited to 'opendc-simulator/opendc-simulator-compute/src/main/java/org') 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 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 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 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 meta) { + this.machine = machine; + this.workload = workload; + this.meta = meta; + } + + @Override + public final Map 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 models; + + public Memory(FlowGraph graph, List 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 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. + * + *

+ * 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 cpus; + + private final Memory memory; + private final List net; + private final List 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 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 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 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 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 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 cpus; + private final Memory memory; + private final List net; + private final List disk; + + private Context(SimBareMetalMachine machine, SimWorkload workload, Map 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 getCpus() { + return cpus; + } + + @Override + public SimMemory getMemory() { + return memory; + } + + @Override + public List getNetworkInterfaces() { + return net; + } + + @Override + public List 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/java/org/opendc/simulator/compute/SimMachine.java b/opendc-simulator/opendc-simulator-compute/src/main/java/org/opendc/simulator/compute/SimMachine.java new file mode 100644 index 00000000..59599875 --- /dev/null +++ b/opendc-simulator/opendc-simulator-compute/src/main/java/org/opendc/simulator/compute/SimMachine.java @@ -0,0 +1,59 @@ +/* + * 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.device.SimPeripheral; +import org.opendc.simulator.compute.model.MachineModel; +import org.opendc.simulator.compute.workload.SimWorkload; + +/** + * A generic machine that is able to execute {@link SimWorkload} objects. + */ +public interface SimMachine { + /** + * Return the model of the machine containing its specifications. + */ + MachineModel getModel(); + + /** + * Return the peripherals attached to the machine. + */ + List getPeripherals(); + + /** + * 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 {@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. + */ + SimMachineContext startWorkload(SimWorkload workload, Map meta); + + /** + * Cancel the active workload on this machine (if any). + */ + 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. + * + *

+ * 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. + *

+ * Users can pass this metadata to the workload via {@link SimMachine#startWorkload(SimWorkload, Map)}. + */ + Map getMeta(); + + /** + * Return the CPUs available on the machine. + */ + List getCpus(); + + /** + * Return the memory interface of the machine. + */ + SimMemory getMemory(); + + /** + * Return the network interfaces available to the workload. + */ + List getNetworkInterfaces(); + + /** + * Return the storage devices available to the workload. + */ + List getStorageInterfaces(); + + /** + * Shutdown the workload. + */ + void shutdown(); +} diff --git a/opendc-simulator/opendc-simulator-compute/src/main/java/org/opendc/simulator/compute/SimMemory.java b/opendc-simulator/opendc-simulator-compute/src/main/java/org/opendc/simulator/compute/SimMemory.java new file mode 100644 index 00000000..4fcc64ab --- /dev/null +++ b/opendc-simulator/opendc-simulator-compute/src/main/java/org/opendc/simulator/compute/SimMemory.java @@ -0,0 +1,42 @@ +/* + * 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 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 extends FlowSink { + /** + * Return the total capacity of the memory (in MBs). + */ + double getCapacity(); + + /** + * Return the models representing the static information of the memory units supporting this interface. + */ + List getModels(); +} diff --git a/opendc-simulator/opendc-simulator-compute/src/main/java/org/opendc/simulator/compute/SimNetworkInterface.java b/opendc-simulator/opendc-simulator-compute/src/main/java/org/opendc/simulator/compute/SimNetworkInterface.java new file mode 100644 index 00000000..4b623e59 --- /dev/null +++ b/opendc-simulator/opendc-simulator-compute/src/main/java/org/opendc/simulator/compute/SimNetworkInterface.java @@ -0,0 +1,51 @@ +/* + * 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.flow2.Inlet; +import org.opendc.simulator.flow2.Outlet; + +/** + * A firmware interface to a network adapter. + */ +public interface SimNetworkInterface { + /** + * Return the name of the network interface. + */ + String getName(); + + /** + * Return the unidirectional bandwidth of the network interface in Mbps. + */ + double getBandwidth(); + + /** + * Return the inlet for the "transmit" channel of the network interface. + */ + Inlet getTx(); + + /** + * Return the outlet for the "receive" channel of the network interface. + */ + Outlet getRx(); +} diff --git a/opendc-simulator/opendc-simulator-compute/src/main/java/org/opendc/simulator/compute/SimProcessingUnit.java b/opendc-simulator/opendc-simulator-compute/src/main/java/org/opendc/simulator/compute/SimProcessingUnit.java new file mode 100644 index 00000000..3dbd3656 --- /dev/null +++ b/opendc-simulator/opendc-simulator-compute/src/main/java/org/opendc/simulator/compute/SimProcessingUnit.java @@ -0,0 +1,62 @@ +/* + * 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.sink.FlowSink; + +/** + * A simulated processing unit. + */ +public interface SimProcessingUnit extends FlowSink { + /** + * Return the base clock frequency of the processing unit (in MHz). + */ + double getFrequency(); + + /** + * Adjust the base clock frequency of the processing unit. + * + *

+ * 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. + */ + void setFrequency(double frequency); + + /** + * The demand on the processing unit. + */ + double getDemand(); + + /** + * The speed of the processing unit. + */ + double getSpeed(); + + /** + * The model representing the static properties of the processing unit. + */ + 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}. + * + *

+ * 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. + *

+ * 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 + * model. + * + * @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 port. + * + * @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. + * + *

+ * 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 now. + */ + 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-compute/src/main/java/org/opendc/simulator/compute/SimPsuFactory.java b/opendc-simulator/opendc-simulator-compute/src/main/java/org/opendc/simulator/compute/SimPsuFactory.java new file mode 100644 index 00000000..872e7016 --- /dev/null +++ b/opendc-simulator/opendc-simulator-compute/src/main/java/org/opendc/simulator/compute/SimPsuFactory.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.compute; + +import org.opendc.simulator.flow2.FlowGraph; + +/** + * A factory interface for {@link SimPsu} implementations. + */ +public interface SimPsuFactory { + /** + * Construct a new {@link SimPsu} for the specified machine. + * + * @param machine The machine to construct the power supply for. + * @param graph The {@link FlowGraph} used for the simulation. + */ + SimPsu newPsu(SimMachine machine, FlowGraph graph); +} diff --git a/opendc-simulator/opendc-simulator-compute/src/main/java/org/opendc/simulator/compute/SimStorageInterface.java b/opendc-simulator/opendc-simulator-compute/src/main/java/org/opendc/simulator/compute/SimStorageInterface.java new file mode 100644 index 00000000..341122dc --- /dev/null +++ b/opendc-simulator/opendc-simulator-compute/src/main/java/org/opendc/simulator/compute/SimStorageInterface.java @@ -0,0 +1,50 @@ +/* + * 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.flow2.Inlet; + +/** + * A firmware interface to a storage device. + */ +public interface SimStorageInterface { + /** + * Return the name of the network interface. + */ + String getName(); + + /** + * Return the capacity of the storage device in MBs. + */ + double getCapacity(); + + /** + * Return the inlet for the read operations of the storage device. + */ + Inlet getRead(); + + /** + * Return the inlet for the write operation of the storage device. + */ + Inlet getWrite(); +} diff --git a/opendc-simulator/opendc-simulator-compute/src/main/java/org/opendc/simulator/compute/device/SimNetworkAdapter.java b/opendc-simulator/opendc-simulator-compute/src/main/java/org/opendc/simulator/compute/device/SimNetworkAdapter.java new file mode 100644 index 00000000..1c16ceff --- /dev/null +++ b/opendc-simulator/opendc-simulator-compute/src/main/java/org/opendc/simulator/compute/device/SimNetworkAdapter.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.compute.device; + +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 {@link SimMachine}. + */ +public abstract class SimNetworkAdapter extends SimNetworkPort implements SimPeripheral { + /** + * Return the unidirectional bandwidth of the network adapter (in Mbps). + */ + public abstract double getBandwidth(); +} diff --git a/opendc-simulator/opendc-simulator-compute/src/main/java/org/opendc/simulator/compute/device/SimPeripheral.java b/opendc-simulator/opendc-simulator-compute/src/main/java/org/opendc/simulator/compute/device/SimPeripheral.java new file mode 100644 index 00000000..40bd268b --- /dev/null +++ b/opendc-simulator/opendc-simulator-compute/src/main/java/org/opendc/simulator/compute/device/SimPeripheral.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.device; + +import org.opendc.simulator.compute.SimMachine; + +/** + * A component that can be attached to a {@link SimMachine}. + *

+ * This interface represents the physical view of the peripheral and should be used to configure the physical properties + * of the peripheral. + */ +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 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 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 machine 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 model 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 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 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 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 getPeripherals() { + return Collections.emptyList(); + } + + @Override + protected Context createContext(SimWorkload workload, Map 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 cpus; + private final SimAbstractMachine.Memory memory; + private final List net; + private final List 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 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 cpuModels = model.getCpus(); + final Inlet[] muxInlets = new Inlet[cpuModels.size()]; + final ArrayList 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 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 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 getCpus() { + return cpus; + } + + @Override + public SimMemory getMemory() { + return memory; + } + + @Override + public List getNetworkInterfaces() { + return net; + } + + @Override + public List 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/java/org/opendc/simulator/compute/kernel/SimHypervisorCounters.java b/opendc-simulator/opendc-simulator-compute/src/main/java/org/opendc/simulator/compute/kernel/SimHypervisorCounters.java new file mode 100644 index 00000000..fc77e9d6 --- /dev/null +++ b/opendc-simulator/opendc-simulator-compute/src/main/java/org/opendc/simulator/compute/kernel/SimHypervisorCounters.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.compute.kernel; + +/** + * Performance counters of a {@link SimHypervisor}. + */ +public interface SimHypervisorCounters { + /** + * Return the amount of time (in milliseconds) the CPUs of the hypervisor were actively running. + */ + long getCpuActiveTime(); + + /** + * Return the amount of time (in milliseconds) the CPUs of the hypervisor were idle. + */ + long getCpuIdleTime(); + + /** + * Return the amount of CPU time (in milliseconds) that virtual machines were ready to run, but were not able to. + */ + long getCpuStealTime(); + + /** + * Return the amount of CPU time (in milliseconds) that was lost due to interference between virtual machines. + */ + long getCpuLostTime(); + + /** + * Synchronize the counter values. + */ + void sync(); +} diff --git a/opendc-simulator/opendc-simulator-compute/src/main/java/org/opendc/simulator/compute/kernel/SimVirtualMachine.java b/opendc-simulator/opendc-simulator-compute/src/main/java/org/opendc/simulator/compute/kernel/SimVirtualMachine.java new file mode 100644 index 00000000..fdf5e47f --- /dev/null +++ b/opendc-simulator/opendc-simulator-compute/src/main/java/org/opendc/simulator/compute/kernel/SimVirtualMachine.java @@ -0,0 +1,50 @@ +/* + * 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 org.opendc.simulator.compute.SimMachine; + +/** + * A virtual {@link SimMachine} running on top of another {@link SimMachine}. + */ +public interface SimVirtualMachine extends SimMachine { + /** + * Return the performance counters associated with the virtual machine. + */ + SimHypervisorCounters getCounters(); + + /** + * Return the CPU usage of the VM in MHz. + */ + double getCpuUsage(); + + /** + * Return the CPU usage of the VM in MHz. + */ + double getCpuDemand(); + + /** + * Return the CPU capacity of the VM in MHz. + */ + double getCpuCapacity(); +} diff --git a/opendc-simulator/opendc-simulator-compute/src/main/java/org/opendc/simulator/compute/kernel/cpufreq/ScalingGovernor.java b/opendc-simulator/opendc-simulator-compute/src/main/java/org/opendc/simulator/compute/kernel/cpufreq/ScalingGovernor.java new file mode 100644 index 00000000..69a371e1 --- /dev/null +++ b/opendc-simulator/opendc-simulator-compute/src/main/java/org/opendc/simulator/compute/kernel/cpufreq/ScalingGovernor.java @@ -0,0 +1,46 @@ +/* + * 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; + +/** + * 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. + * + *

+ * Each of the scaling governors implements a single, possibly parametrized, performance scaling algorithm. + * + * @see documentation of the Linux CPUFreq subsystem. + */ +public interface ScalingGovernor { + /** + * This method is invoked when the governor is started. + */ + default void onStart() {} + + /** + * This method is invoked when the governor should re-decide the frequency limits. + * + * @param load The load of the system. + */ + 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 performance scaling governor. + * + *

+ * 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 powersave scaling governor. + * + *

+ * 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 conservative 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 conservative 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 ondemand 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/java/org/opendc/simulator/compute/kernel/cpufreq/ScalingPolicy.java b/opendc-simulator/opendc-simulator-compute/src/main/java/org/opendc/simulator/compute/kernel/cpufreq/ScalingPolicy.java new file mode 100644 index 00000000..0cdb7a0b --- /dev/null +++ b/opendc-simulator/opendc-simulator-compute/src/main/java/org/opendc/simulator/compute/kernel/cpufreq/ScalingPolicy.java @@ -0,0 +1,56 @@ +/* + * 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; + +import org.opendc.simulator.compute.SimProcessingUnit; + +/** + * 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. + */ + SimProcessingUnit getCpu(); + + /** + * Return the target frequency which the CPU should attempt to attain. + */ + double getTarget(); + + /** + * Set the target frequency which the CPU should attempt to attain. + */ + void setTarget(double target); + + /** + * Return the minimum frequency to which the CPU may scale. + */ + 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 cache = new WeakHashMap<>(); + + /** + * The set of members active in this domain. + */ + private final ArrayList activeKeys = new ArrayList<>(); + + /** + * Queue of participants that will be removed or added to the active groups. + */ + private final ArrayDeque participants = new ArrayDeque<>(); + + /** + * Join this interference domain with the specified profile 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 member as active in this interference domain. + */ + void activate(VmInterferenceMember member) { + final ArrayList activeKeys = this.activeKeys; + int pos = Collections.binarySearch(activeKeys, member); + if (pos < 0) { + activeKeys.add(-pos - 1, member); + } + + computeActiveGroups(activeKeys, member); + } + + /** + * Mark the specified member as inactive in this interference domain. + */ + void deactivate(VmInterferenceMember member) { + final ArrayList activeKeys = this.activeKeys; + activeKeys.remove(member); + computeActiveGroups(activeKeys, member); + } + + /** + * (Re-)compute the active groups. + */ + private void computeActiveGroups(ArrayList activeKeys, VmInterferenceMember member) { + if (activeKeys.isEmpty()) { + return; + } + + final int[] groups = member.membership; + final int[][] members = member.members; + final ArrayDeque 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 { + 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 idMapping; + private final int[][] members; + private final int[][] membership; + private final double[] targets; + private final double[] scores; + + private VmInterferenceModel( + Map 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 id. + * + * @param id The identifier of the virtual machine. + * @return A {@link VmInterferenceProfile} representing the virtual machine as part of interference model or + * null 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> members; + private final TreeSet 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 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> 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 idMapping = new HashMap<>(); + TreeMap> 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 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 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/java/org/opendc/simulator/compute/kernel/interference/VmInterferenceProfile.java b/opendc-simulator/opendc-simulator-compute/src/main/java/org/opendc/simulator/compute/kernel/interference/VmInterferenceProfile.java new file mode 100644 index 00000000..3f0c0a88 --- /dev/null +++ b/opendc-simulator/opendc-simulator-compute/src/main/java/org/opendc/simulator/compute/kernel/interference/VmInterferenceProfile.java @@ -0,0 +1,60 @@ +/* + * 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; + +/** + * A profile of a particular virtual machine describing its interference pattern with other virtual machines. + */ +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; + + /** + * Construct a {@link VmInterferenceProfile}. + */ + 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; + } + + /** + * Create a new {@link VmInterferenceMember} based on this profile for the specified domain. + */ + 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 cpus; + private final List memory; + private final List net; + private final List 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 cpus, + Iterable memory, + Iterable net, + Iterable 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 cpus, Iterable 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 getCpus() { + return Collections.unmodifiableList(cpus); + } + + /** + * Return the memory units of this machine. + */ + public List getMemory() { + return Collections.unmodifiableList(memory); + } + + /** + * Return the network adapters of this machine. + */ + public List getNetwork() { + return Collections.unmodifiableList(net); + } + + /** + * Return the storage devices of this machine. + */ + public List 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/java/org/opendc/simulator/compute/power/CpuPowerModel.java b/opendc-simulator/opendc-simulator-compute/src/main/java/org/opendc/simulator/compute/power/CpuPowerModel.java new file mode 100644 index 00000000..e023d098 --- /dev/null +++ b/opendc-simulator/opendc-simulator-compute/src/main/java/org/opendc/simulator/compute/power/CpuPowerModel.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.compute.power; + +import org.opendc.simulator.compute.SimMachine; + +/** + * A model for estimating the power usage of a {@link SimMachine} based on the CPU usage. + */ +public interface CpuPowerModel { + /** + * Computes CPU power consumption for each host. + * + * @param utilization The CPU utilization percentage. + * @return A double value of CPU power consumption (in W). + */ + 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 + * Fan et al., Power provisioning for a warehouse-sized computer, ACM SIGARCH'07 + */ + 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. + * + *

+ * 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 Machines used in the SPEC benchmark + */ + 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 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 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..0bd2b2eb --- /dev/null +++ b/opendc-simulator/opendc-simulator-compute/src/main/java/org/opendc/simulator/compute/workload/SimTrace.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.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 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. + * + *

+ * 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, FlowStageLogic { + private SimMachineContext ctx; + private FlowStage stage; + private OutPort[] outputs; + private int index; + private int coreCount; + + 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) { + this.ctx = ctx; + + final FlowGraph graph = ctx.getGraph(); + final List 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 void onStop(SimMachineContext ctx) { + this.ctx = null; + + final FlowStage stage = this.stage; + + if (stage != null) { + this.stage = null; + stage.close(); + } + } + + @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; + } + } +} 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/java/org/opendc/simulator/compute/workload/SimWorkload.java b/opendc-simulator/opendc-simulator-compute/src/main/java/org/opendc/simulator/compute/workload/SimWorkload.java new file mode 100644 index 00000000..7be51265 --- /dev/null +++ b/opendc-simulator/opendc-simulator-compute/src/main/java/org/opendc/simulator/compute/workload/SimWorkload.java @@ -0,0 +1,48 @@ +/* + * 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 org.opendc.simulator.compute.SimMachineContext; + +/** + * A model that characterizes the runtime behavior of some particular workload. + * + *

+ * Workloads are stateful objects that may be paused and resumed at a later moment. As such, be careful when using the + * same {@link SimWorkload} from multiple contexts. + */ +public interface SimWorkload { + /** + * This method is invoked when the workload is started. + * + * @param ctx The execution context in which the machine runs. + */ + void onStart(SimMachineContext ctx); + + /** + * This method is invoked when the workload is stopped. + * + * @param ctx The execution context in which the machine runs. + */ + void onStop(SimMachineContext ctx); +} diff --git a/opendc-simulator/opendc-simulator-compute/src/main/java/org/opendc/simulator/compute/workload/SimWorkloadLifecycle.java b/opendc-simulator/opendc-simulator-compute/src/main/java/org/opendc/simulator/compute/workload/SimWorkloadLifecycle.java new file mode 100644 index 00000000..f0e2561f --- /dev/null +++ b/opendc-simulator/opendc-simulator-compute/src/main/java/org/opendc/simulator/compute/workload/SimWorkloadLifecycle.java @@ -0,0 +1,60 @@ +/* + * 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.HashSet; +import org.opendc.simulator.compute.SimMachineContext; + +/** + * A helper class to manage the lifecycle of a {@link SimWorkload}. + */ +public final class SimWorkloadLifecycle { + private final SimMachineContext ctx; + private final HashSet waiting = new HashSet<>(); + + /** + * Construct a {@link SimWorkloadLifecycle} instance. + * + * @param ctx The {@link SimMachineContext} of the workload. + */ + public SimWorkloadLifecycle(SimMachineContext ctx) { + this.ctx = ctx; + } + + /** + * 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 waiting = SimWorkloadLifecycle.this.waiting; + if (waiting.remove(this) && waiting.isEmpty()) { + ctx.shutdown(); + } + } + }; + waiting.add(completer); + return completer; + } +} -- cgit v1.2.3 From 037c0600d0fe3ec699f239c41ab0e60a458484d7 Mon Sep 17 00:00:00 2001 From: Fabian Mastenbroek Date: Tue, 18 Oct 2022 11:35:56 +0200 Subject: perf(sim/compute): Optimize workload implementation of SimTrace This change updates the workload implementation of SimTrace by introducing multiple implementations of the FlowStageLogic interface, which is selected dynamically when the workload starts. This change helps eliminate the unecessary division (and memory fetch) when running a workload with just a single CPU. --- .../simulator/compute/workload/SimTrace.java | 155 ++++++++++++++++++--- 1 file changed, 137 insertions(+), 18 deletions(-) (limited to 'opendc-simulator/opendc-simulator-compute/src/main/java/org') 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 index 0bd2b2eb..12a567ff 100644 --- 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 @@ -203,12 +203,8 @@ public final class SimTrace { /** * Implementation of {@link SimWorkload} that executes a trace. */ - private static class Workload implements SimWorkload, FlowStageLogic { - private SimMachineContext ctx; - private FlowStage stage; - private OutPort[] outputs; - private int index; - private int coreCount; + private static class Workload implements SimWorkload { + private WorkloadStageLogic logic; private final long offset; private final double[] usageCol; @@ -226,7 +222,137 @@ public final class SimTrace { @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 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 cpus = ctx.getCpus(); @@ -246,18 +372,6 @@ public final class SimTrace { } } - @Override - public void onStop(SimMachineContext ctx) { - this.ctx = null; - - final FlowStage stage = this.stage; - - if (stage != null) { - this.stage = null; - stage.close(); - } - } - @Override public long onUpdate(FlowStage ctx, long now) { int size = this.size; @@ -299,5 +413,10 @@ public final class SimTrace { return deadline + offset; } + + @Override + public FlowStage getStage() { + return stage; + } } } -- cgit v1.2.3