summaryrefslogtreecommitdiff
path: root/opendc-simulator/opendc-simulator-compute/src/main/java
diff options
context:
space:
mode:
Diffstat (limited to 'opendc-simulator/opendc-simulator-compute/src/main/java')
-rw-r--r--opendc-simulator/opendc-simulator-compute/src/main/java/org/opendc/simulator/compute/SimAbstractMachine.java327
-rw-r--r--opendc-simulator/opendc-simulator-compute/src/main/java/org/opendc/simulator/compute/SimBareMetalMachine.java298
-rw-r--r--opendc-simulator/opendc-simulator-compute/src/main/java/org/opendc/simulator/compute/SimMachine.java59
-rw-r--r--opendc-simulator/opendc-simulator-compute/src/main/java/org/opendc/simulator/compute/SimMachineContext.java74
-rw-r--r--opendc-simulator/opendc-simulator-compute/src/main/java/org/opendc/simulator/compute/SimMemory.java42
-rw-r--r--opendc-simulator/opendc-simulator-compute/src/main/java/org/opendc/simulator/compute/SimNetworkInterface.java51
-rw-r--r--opendc-simulator/opendc-simulator-compute/src/main/java/org/opendc/simulator/compute/SimProcessingUnit.java62
-rw-r--r--opendc-simulator/opendc-simulator-compute/src/main/java/org/opendc/simulator/compute/SimPsu.java71
-rw-r--r--opendc-simulator/opendc-simulator-compute/src/main/java/org/opendc/simulator/compute/SimPsuFactories.java214
-rw-r--r--opendc-simulator/opendc-simulator-compute/src/main/java/org/opendc/simulator/compute/SimPsuFactory.java38
-rw-r--r--opendc-simulator/opendc-simulator-compute/src/main/java/org/opendc/simulator/compute/SimStorageInterface.java50
-rw-r--r--opendc-simulator/opendc-simulator-compute/src/main/java/org/opendc/simulator/compute/device/SimNetworkAdapter.java36
-rw-r--r--opendc-simulator/opendc-simulator-compute/src/main/java/org/opendc/simulator/compute/device/SimPeripheral.java33
-rw-r--r--opendc-simulator/opendc-simulator-compute/src/main/java/org/opendc/simulator/compute/kernel/SimHypervisor.java911
-rw-r--r--opendc-simulator/opendc-simulator-compute/src/main/java/org/opendc/simulator/compute/kernel/SimHypervisorCounters.java53
-rw-r--r--opendc-simulator/opendc-simulator-compute/src/main/java/org/opendc/simulator/compute/kernel/SimVirtualMachine.java50
-rw-r--r--opendc-simulator/opendc-simulator-compute/src/main/java/org/opendc/simulator/compute/kernel/cpufreq/ScalingGovernor.java46
-rw-r--r--opendc-simulator/opendc-simulator-compute/src/main/java/org/opendc/simulator/compute/kernel/cpufreq/ScalingGovernorFactory.java33
-rw-r--r--opendc-simulator/opendc-simulator-compute/src/main/java/org/opendc/simulator/compute/kernel/cpufreq/ScalingGovernors.java190
-rw-r--r--opendc-simulator/opendc-simulator-compute/src/main/java/org/opendc/simulator/compute/kernel/cpufreq/ScalingPolicy.java56
-rw-r--r--opendc-simulator/opendc-simulator-compute/src/main/java/org/opendc/simulator/compute/kernel/interference/VmInterferenceDomain.java136
-rw-r--r--opendc-simulator/opendc-simulator-compute/src/main/java/org/opendc/simulator/compute/kernel/interference/VmInterferenceMember.java177
-rw-r--r--opendc-simulator/opendc-simulator-compute/src/main/java/org/opendc/simulator/compute/kernel/interference/VmInterferenceModel.java185
-rw-r--r--opendc-simulator/opendc-simulator-compute/src/main/java/org/opendc/simulator/compute/kernel/interference/VmInterferenceProfile.java60
-rw-r--r--opendc-simulator/opendc-simulator-compute/src/main/java/org/opendc/simulator/compute/model/MachineModel.java149
-rw-r--r--opendc-simulator/opendc-simulator-compute/src/main/java/org/opendc/simulator/compute/model/MemoryUnit.java100
-rw-r--r--opendc-simulator/opendc-simulator-compute/src/main/java/org/opendc/simulator/compute/model/NetworkAdapter.java88
-rw-r--r--opendc-simulator/opendc-simulator-compute/src/main/java/org/opendc/simulator/compute/model/ProcessingNode.java100
-rw-r--r--opendc-simulator/opendc-simulator-compute/src/main/java/org/opendc/simulator/compute/model/ProcessingUnit.java86
-rw-r--r--opendc-simulator/opendc-simulator-compute/src/main/java/org/opendc/simulator/compute/model/StorageDevice.java112
-rw-r--r--opendc-simulator/opendc-simulator-compute/src/main/java/org/opendc/simulator/compute/power/CpuPowerModel.java38
-rw-r--r--opendc-simulator/opendc-simulator-compute/src/main/java/org/opendc/simulator/compute/power/CpuPowerModels.java330
-rw-r--r--opendc-simulator/opendc-simulator-compute/src/main/java/org/opendc/simulator/compute/workload/SimFlopsWorkload.java141
-rw-r--r--opendc-simulator/opendc-simulator-compute/src/main/java/org/opendc/simulator/compute/workload/SimRuntimeWorkload.java131
-rw-r--r--opendc-simulator/opendc-simulator-compute/src/main/java/org/opendc/simulator/compute/workload/SimTrace.java303
-rw-r--r--opendc-simulator/opendc-simulator-compute/src/main/java/org/opendc/simulator/compute/workload/SimTraceFragment.java94
-rw-r--r--opendc-simulator/opendc-simulator-compute/src/main/java/org/opendc/simulator/compute/workload/SimWorkload.java48
-rw-r--r--opendc-simulator/opendc-simulator-compute/src/main/java/org/opendc/simulator/compute/workload/SimWorkloadLifecycle.java60
38 files changed, 5032 insertions, 0 deletions
diff --git a/opendc-simulator/opendc-simulator-compute/src/main/java/org/opendc/simulator/compute/SimAbstractMachine.java b/opendc-simulator/opendc-simulator-compute/src/main/java/org/opendc/simulator/compute/SimAbstractMachine.java
new file mode 100644
index 00000000..cf5aed03
--- /dev/null
+++ b/opendc-simulator/opendc-simulator-compute/src/main/java/org/opendc/simulator/compute/SimAbstractMachine.java
@@ -0,0 +1,327 @@
+/*
+ * Copyright (c) 2022 AtLarge Research
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
+ * of this software and associated documentation files (the "Software"), to deal
+ * in the Software without restriction, including without limitation the rights
+ * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+ * copies of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included in all
+ * copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
+ * SOFTWARE.
+ */
+
+package org.opendc.simulator.compute;
+
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+import org.opendc.simulator.compute.device.SimNetworkAdapter;
+import org.opendc.simulator.compute.model.MachineModel;
+import org.opendc.simulator.compute.model.MemoryUnit;
+import org.opendc.simulator.compute.workload.SimWorkload;
+import org.opendc.simulator.flow2.FlowGraph;
+import org.opendc.simulator.flow2.Inlet;
+import org.opendc.simulator.flow2.Outlet;
+import org.opendc.simulator.flow2.sink.SimpleFlowSink;
+import org.opendc.simulator.flow2.util.FlowTransformer;
+import org.opendc.simulator.flow2.util.FlowTransforms;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+/**
+ * Abstract implementation of the {@link SimMachine} interface.
+ */
+public abstract class SimAbstractMachine implements SimMachine {
+ private static final Logger LOGGER = LoggerFactory.getLogger(SimAbstractMachine.class);
+ private final MachineModel model;
+
+ private Context activeContext;
+
+ /**
+ * Construct a {@link SimAbstractMachine} instance.
+ *
+ * @param model The model of the machine.
+ */
+ public SimAbstractMachine(MachineModel model) {
+ this.model = model;
+ }
+
+ @Override
+ public final MachineModel getModel() {
+ return model;
+ }
+
+ @Override
+ public final SimMachineContext startWorkload(SimWorkload workload, Map<String, Object> meta) {
+ if (activeContext != null) {
+ throw new IllegalStateException("A machine cannot run multiple workloads concurrently");
+ }
+
+ final Context ctx = createContext(workload, new HashMap<>(meta));
+ ctx.start();
+ return ctx;
+ }
+
+ @Override
+ public final void cancel() {
+ final Context context = activeContext;
+ if (context != null) {
+ context.shutdown();
+ }
+ }
+
+ /**
+ * Construct a new {@link Context} instance representing the active execution.
+ *
+ * @param workload The workload to start on the machine.
+ * @param meta The metadata to pass to the workload.
+ */
+ protected abstract Context createContext(SimWorkload workload, Map<String, Object> meta);
+
+ /**
+ * Return the active {@link Context} instance (if any).
+ */
+ protected Context getActiveContext() {
+ return activeContext;
+ }
+
+ /**
+ * The execution context in which the workload runs.
+ */
+ public abstract static class Context implements SimMachineContext {
+ private final SimAbstractMachine machine;
+ private final SimWorkload workload;
+ private final Map<String, Object> meta;
+ private boolean isClosed;
+
+ /**
+ * Construct a new {@link Context} instance.
+ *
+ * @param machine The {@link SimAbstractMachine} to which the context belongs.
+ * @param workload The {@link SimWorkload} to which the context belongs.
+ * @param meta The metadata passed to the context.
+ */
+ public Context(SimAbstractMachine machine, SimWorkload workload, Map<String, Object> meta) {
+ this.machine = machine;
+ this.workload = workload;
+ this.meta = meta;
+ }
+
+ @Override
+ public final Map<String, Object> getMeta() {
+ return meta;
+ }
+
+ @Override
+ public final void shutdown() {
+ if (isClosed) {
+ return;
+ }
+
+ isClosed = true;
+ final SimAbstractMachine machine = this.machine;
+ assert machine.activeContext == this : "Invariant violation: multiple contexts active for a single machine";
+ machine.activeContext = null;
+
+ // Cancel all the resources associated with the machine
+ doCancel();
+
+ try {
+ workload.onStop(this);
+ } catch (Exception cause) {
+ LOGGER.warn("Workload failed during onStop callback", cause);
+ }
+ }
+
+ /**
+ * Start this context.
+ */
+ final void start() {
+ try {
+ machine.activeContext = this;
+ workload.onStart(this);
+ } catch (Exception cause) {
+ LOGGER.warn("Workload failed during onStart callback", cause);
+ shutdown();
+ }
+ }
+
+ /**
+ * Run the stop procedures for the resources associated with the machine.
+ */
+ protected void doCancel() {
+ final FlowGraph graph = getMemory().getInput().getGraph();
+
+ for (SimProcessingUnit cpu : getCpus()) {
+ final Inlet inlet = cpu.getInput();
+ graph.disconnect(inlet);
+ }
+
+ graph.disconnect(getMemory().getInput());
+
+ for (SimNetworkInterface ifx : getNetworkInterfaces()) {
+ ((NetworkAdapter) ifx).disconnect();
+ }
+
+ for (SimStorageInterface storage : getStorageInterfaces()) {
+ StorageDevice impl = (StorageDevice) storage;
+ graph.disconnect(impl.getRead());
+ graph.disconnect(impl.getWrite());
+ }
+ }
+
+ @Override
+ public String toString() {
+ return "SimAbstractMachine.Context";
+ }
+ }
+
+ /**
+ * The [SimMemory] implementation for a machine.
+ */
+ public static final class Memory implements SimMemory {
+ private final SimpleFlowSink sink;
+ private final List<MemoryUnit> models;
+
+ public Memory(FlowGraph graph, List<MemoryUnit> models) {
+ long memorySize = 0;
+ for (MemoryUnit mem : models) {
+ memorySize += mem.getSize();
+ }
+
+ this.sink = new SimpleFlowSink(graph, (float) memorySize);
+ this.models = models;
+ }
+
+ @Override
+ public double getCapacity() {
+ return sink.getCapacity();
+ }
+
+ @Override
+ public List<MemoryUnit> getModels() {
+ return models;
+ }
+
+ @Override
+ public Inlet getInput() {
+ return sink.getInput();
+ }
+
+ @Override
+ public String toString() {
+ return "SimAbstractMachine.Memory";
+ }
+ }
+
+ /**
+ * A {@link SimNetworkAdapter} implementation for a machine.
+ */
+ public static class NetworkAdapter extends SimNetworkAdapter implements SimNetworkInterface {
+ private final org.opendc.simulator.compute.model.NetworkAdapter model;
+ private final FlowTransformer tx;
+ private final FlowTransformer rx;
+ private final String name;
+
+ /**
+ * Construct a {@link NetworkAdapter}.
+ */
+ public NetworkAdapter(FlowGraph graph, org.opendc.simulator.compute.model.NetworkAdapter model, int index) {
+ this.model = model;
+ this.tx = new FlowTransformer(graph, FlowTransforms.noop());
+ this.rx = new FlowTransformer(graph, FlowTransforms.noop());
+ this.name = "eth" + index;
+ }
+
+ @Override
+ public String getName() {
+ return name;
+ }
+
+ @Override
+ public Inlet getTx() {
+ return tx.getInput();
+ }
+
+ @Override
+ public Outlet getRx() {
+ return rx.getOutput();
+ }
+
+ @Override
+ public double getBandwidth() {
+ return model.getBandwidth();
+ }
+
+ @Override
+ protected Outlet getOutlet() {
+ return tx.getOutput();
+ }
+
+ @Override
+ protected Inlet getInlet() {
+ return rx.getInput();
+ }
+
+ @Override
+ public String toString() {
+ return "SimAbstractMachine.NetworkAdapterImpl[name=" + name + ", bandwidth=" + model.getBandwidth()
+ + "Mbps]";
+ }
+ }
+
+ /**
+ * A {@link SimStorageInterface} implementation for a machine.
+ */
+ public static class StorageDevice implements SimStorageInterface {
+ private final org.opendc.simulator.compute.model.StorageDevice model;
+ private final SimpleFlowSink read;
+ private final SimpleFlowSink write;
+ private final String name;
+
+ /**
+ * Construct a {@link StorageDevice}.
+ */
+ public StorageDevice(FlowGraph graph, org.opendc.simulator.compute.model.StorageDevice model, int index) {
+ this.model = model;
+ this.read = new SimpleFlowSink(graph, (float) model.getReadBandwidth());
+ this.write = new SimpleFlowSink(graph, (float) model.getWriteBandwidth());
+ this.name = "disk" + index;
+ }
+
+ @Override
+ public String getName() {
+ return name;
+ }
+
+ @Override
+ public Inlet getRead() {
+ return read.getInput();
+ }
+
+ @Override
+ public Inlet getWrite() {
+ return write.getInput();
+ }
+
+ @Override
+ public double getCapacity() {
+ return model.getCapacity();
+ }
+
+ @Override
+ public String toString() {
+ return "SimAbstractMachine.StorageDeviceImpl[name=" + name + ", capacity=" + model.getCapacity() + "MB]";
+ }
+ }
+}
diff --git a/opendc-simulator/opendc-simulator-compute/src/main/java/org/opendc/simulator/compute/SimBareMetalMachine.java b/opendc-simulator/opendc-simulator-compute/src/main/java/org/opendc/simulator/compute/SimBareMetalMachine.java
new file mode 100644
index 00000000..aa7502d6
--- /dev/null
+++ b/opendc-simulator/opendc-simulator-compute/src/main/java/org/opendc/simulator/compute/SimBareMetalMachine.java
@@ -0,0 +1,298 @@
+/*
+ * Copyright (c) 2022 AtLarge Research
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
+ * of this software and associated documentation files (the "Software"), to deal
+ * in the Software without restriction, including without limitation the rights
+ * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+ * copies of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included in all
+ * copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
+ * SOFTWARE.
+ */
+
+package org.opendc.simulator.compute;
+
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.List;
+import java.util.Map;
+import org.opendc.simulator.compute.device.SimPeripheral;
+import org.opendc.simulator.compute.model.MachineModel;
+import org.opendc.simulator.compute.model.ProcessingUnit;
+import org.opendc.simulator.compute.workload.SimWorkload;
+import org.opendc.simulator.flow2.FlowGraph;
+import org.opendc.simulator.flow2.InPort;
+import org.opendc.simulator.flow2.Inlet;
+
+/**
+ * A simulated bare-metal machine that is able to run a single workload.
+ *
+ * <p>
+ * A {@link SimBareMetalMachine} is a stateful object, and you should be careful when operating this object concurrently. For
+ * example, the class expects only a single concurrent call to {@link #startWorkload(SimWorkload, Map)}.
+ */
+public final class SimBareMetalMachine extends SimAbstractMachine {
+ /**
+ * The {@link FlowGraph} in which the simulation takes places.
+ */
+ private final FlowGraph graph;
+
+ /**
+ * The {@link SimPsu} of this bare metal machine.
+ */
+ private final SimPsu psu;
+
+ /**
+ * The resources of this machine.
+ */
+ private final List<Cpu> cpus;
+
+ private final Memory memory;
+ private final List<NetworkAdapter> net;
+ private final List<StorageDevice> disk;
+
+ /**
+ * Construct a {@link SimBareMetalMachine} instance.
+ *
+ * @param graph The {@link FlowGraph} to which the machine belongs.
+ * @param model The machine model to simulate.
+ * @param psuFactory The {@link SimPsuFactory} to construct the power supply of the machine.
+ */
+ private SimBareMetalMachine(FlowGraph graph, MachineModel model, SimPsuFactory psuFactory) {
+ super(model);
+
+ this.graph = graph;
+ this.psu = psuFactory.newPsu(this, graph);
+
+ int cpuIndex = 0;
+ final ArrayList<Cpu> cpus = new ArrayList<>();
+ this.cpus = cpus;
+ for (ProcessingUnit cpu : model.getCpus()) {
+ cpus.add(new Cpu(psu, cpu, cpuIndex++));
+ }
+
+ this.memory = new Memory(graph, model.getMemory());
+
+ int netIndex = 0;
+ final ArrayList<NetworkAdapter> net = new ArrayList<>();
+ this.net = net;
+ for (org.opendc.simulator.compute.model.NetworkAdapter adapter : model.getNetwork()) {
+ net.add(new NetworkAdapter(graph, adapter, netIndex++));
+ }
+
+ int diskIndex = 0;
+ final ArrayList<StorageDevice> disk = new ArrayList<>();
+ this.disk = disk;
+ for (org.opendc.simulator.compute.model.StorageDevice device : model.getStorage()) {
+ disk.add(new StorageDevice(graph, device, diskIndex++));
+ }
+ }
+
+ /**
+ * Create a {@link SimBareMetalMachine} instance.
+ *
+ * @param graph The {@link FlowGraph} to which the machine belongs.
+ * @param model The machine model to simulate.
+ * @param psuFactory The {@link SimPsuFactory} to construct the power supply of the machine.
+ */
+ public static SimBareMetalMachine create(FlowGraph graph, MachineModel model, SimPsuFactory psuFactory) {
+ return new SimBareMetalMachine(graph, model, psuFactory);
+ }
+
+ /**
+ * Create a {@link SimBareMetalMachine} instance with a no-op PSU.
+ *
+ * @param graph The {@link FlowGraph} to which the machine belongs.
+ * @param model The machine model to simulate.
+ */
+ public static SimBareMetalMachine create(FlowGraph graph, MachineModel model) {
+ return new SimBareMetalMachine(graph, model, SimPsuFactories.noop());
+ }
+
+ /**
+ * Return the {@link SimPsu} belonging to this bare metal machine.
+ */
+ public SimPsu getPsu() {
+ return psu;
+ }
+
+ /**
+ * Return the list of peripherals attached to this bare metal machine.
+ */
+ @Override
+ public List<? extends SimPeripheral> getPeripherals() {
+ return Collections.unmodifiableList(net);
+ }
+
+ /**
+ * Return the CPU capacity of the machine in MHz.
+ */
+ public double getCpuCapacity() {
+ final Context context = (Context) getActiveContext();
+
+ if (context == null) {
+ return 0.0;
+ }
+
+ float capacity = 0.f;
+
+ for (SimProcessingUnit cpu : context.cpus) {
+ capacity += cpu.getFrequency();
+ }
+
+ return capacity;
+ }
+
+ /**
+ * The CPU demand of the machine in MHz.
+ */
+ public double getCpuDemand() {
+ final Context context = (Context) getActiveContext();
+
+ if (context == null) {
+ return 0.0;
+ }
+
+ float demand = 0.f;
+
+ for (SimProcessingUnit cpu : context.cpus) {
+ demand += cpu.getDemand();
+ }
+
+ return demand;
+ }
+
+ /**
+ * The CPU usage of the machine in MHz.
+ */
+ public double getCpuUsage() {
+ final Context context = (Context) getActiveContext();
+
+ if (context == null) {
+ return 0.0;
+ }
+
+ float rate = 0.f;
+
+ for (SimProcessingUnit cpu : context.cpus) {
+ rate += cpu.getSpeed();
+ }
+
+ return rate;
+ }
+
+ @Override
+ protected SimAbstractMachine.Context createContext(SimWorkload workload, Map<String, Object> meta) {
+ return new Context(this, workload, meta);
+ }
+
+ /**
+ * The execution context for a {@link SimBareMetalMachine}.
+ */
+ private static final class Context extends SimAbstractMachine.Context {
+ private final FlowGraph graph;
+ private final List<Cpu> cpus;
+ private final Memory memory;
+ private final List<NetworkAdapter> net;
+ private final List<StorageDevice> disk;
+
+ private Context(SimBareMetalMachine machine, SimWorkload workload, Map<String, Object> meta) {
+ super(machine, workload, meta);
+
+ this.graph = machine.graph;
+ this.cpus = machine.cpus;
+ this.memory = machine.memory;
+ this.net = machine.net;
+ this.disk = machine.disk;
+ }
+
+ @Override
+ public FlowGraph getGraph() {
+ return graph;
+ }
+
+ @Override
+ public List<? extends SimProcessingUnit> getCpus() {
+ return cpus;
+ }
+
+ @Override
+ public SimMemory getMemory() {
+ return memory;
+ }
+
+ @Override
+ public List<? extends SimNetworkInterface> getNetworkInterfaces() {
+ return net;
+ }
+
+ @Override
+ public List<? extends SimStorageInterface> getStorageInterfaces() {
+ return disk;
+ }
+ }
+
+ /**
+ * A {@link SimProcessingUnit} of a bare-metal machine.
+ */
+ private static final class Cpu implements SimProcessingUnit {
+ private final SimPsu psu;
+ private final ProcessingUnit model;
+ private final InPort port;
+
+ private Cpu(SimPsu psu, ProcessingUnit model, int id) {
+ this.psu = psu;
+ this.model = model;
+ this.port = psu.getCpuPower(id, model);
+
+ this.port.pull((float) model.getFrequency());
+ }
+
+ @Override
+ public double getFrequency() {
+ return port.getCapacity();
+ }
+
+ @Override
+ public void setFrequency(double frequency) {
+ // Clamp the capacity of the CPU between [0.0, maxFreq]
+ frequency = Math.max(0, Math.min(model.getFrequency(), frequency));
+ psu.setCpuFrequency(port, frequency);
+ }
+
+ @Override
+ public double getDemand() {
+ return port.getDemand();
+ }
+
+ @Override
+ public double getSpeed() {
+ return port.getRate();
+ }
+
+ @Override
+ public ProcessingUnit getModel() {
+ return model;
+ }
+
+ @Override
+ public Inlet getInput() {
+ return port;
+ }
+
+ @Override
+ public String toString() {
+ return "SimBareMetalMachine.Cpu[model=" + model + "]";
+ }
+ }
+}
diff --git a/opendc-simulator/opendc-simulator-compute/src/main/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<? extends SimPeripheral> 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<String, Object> 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.
+ *
+ * <p>
+ * This interface represents the interface between the running image (e.g. operating system) and the physical
+ * or virtual firmware on which the image runs.
+ */
+public interface SimMachineContext {
+ /**
+ * Return the {@link FlowGraph} in which the workload executes.
+ */
+ FlowGraph getGraph();
+
+ /**
+ * Return the metadata associated with the context.
+ * <p>
+ * Users can pass this metadata to the workload via {@link SimMachine#startWorkload(SimWorkload, Map)}.
+ */
+ Map<String, Object> getMeta();
+
+ /**
+ * Return the CPUs available on the machine.
+ */
+ List<? extends SimProcessingUnit> getCpus();
+
+ /**
+ * Return the memory interface of the machine.
+ */
+ SimMemory getMemory();
+
+ /**
+ * Return the network interfaces available to the workload.
+ */
+ List<? extends SimNetworkInterface> getNetworkInterfaces();
+
+ /**
+ * Return the storage devices available to the workload.
+ */
+ List<? extends SimStorageInterface> getStorageInterfaces();
+
+ /**
+ * Shutdown the workload.
+ */
+ void shutdown();
+}
diff --git a/opendc-simulator/opendc-simulator-compute/src/main/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<MemoryUnit> 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.
+ *
+ * <p>
+ * The CPU may or may not round the new frequency to one of its pre-defined frequency steps.
+ *
+ * @param frequency The new frequency to set the clock of the processing unit to.
+ * @throws UnsupportedOperationException if the base clock cannot be adjusted.
+ */
+ 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}.
+ *
+ * <p>
+ * This class manages the computation of power usage for a {@link SimBareMetalMachine} based on the resource usage.
+ */
+public abstract class SimPsu extends SimPowerInlet {
+ /**
+ * Return the power demand of the machine (in W) measured in the PSU.
+ * <p>
+ * This method provides access to the power consumption of the machine before PSU losses are applied.
+ */
+ public abstract double getPowerDemand();
+
+ /**
+ * Return the instantaneous power usage of the machine (in W) measured at the inlet of the power supply.
+ */
+ public abstract double getPowerUsage();
+
+ /**
+ * Return the cumulated energy usage of the machine (in J) measured at the inlet of the powers supply.
+ */
+ public abstract double getEnergyUsage();
+
+ /**
+ * Return an {@link InPort} that converts processing demand (in MHz) into energy demand (J) for the specified CPU
+ * <code>model</code>.
+ *
+ * @param id The unique identifier of the CPU for this machine.
+ * @param model The details of the processing unit.
+ */
+ abstract InPort getCpuPower(int id, ProcessingUnit model);
+
+ /**
+ * This method is invoked when the CPU frequency is changed for the specified <code>port</code>.
+ *
+ * @param port The {@link InPort} for which the capacity is changed.
+ * @param capacity The capacity to change to.
+ */
+ void setCpuFrequency(InPort port, double capacity) {
+ port.pull((float) capacity);
+ }
+}
diff --git a/opendc-simulator/opendc-simulator-compute/src/main/java/org/opendc/simulator/compute/SimPsuFactories.java b/opendc-simulator/opendc-simulator-compute/src/main/java/org/opendc/simulator/compute/SimPsuFactories.java
new file mode 100644
index 00000000..52d04052
--- /dev/null
+++ b/opendc-simulator/opendc-simulator-compute/src/main/java/org/opendc/simulator/compute/SimPsuFactories.java
@@ -0,0 +1,214 @@
+/*
+ * Copyright (c) 2022 AtLarge Research
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
+ * of this software and associated documentation files (the "Software"), to deal
+ * in the Software without restriction, including without limitation the rights
+ * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+ * copies of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included in all
+ * copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
+ * SOFTWARE.
+ */
+
+package org.opendc.simulator.compute;
+
+import java.time.Clock;
+import org.jetbrains.annotations.NotNull;
+import org.opendc.simulator.compute.model.ProcessingUnit;
+import org.opendc.simulator.compute.power.CpuPowerModel;
+import org.opendc.simulator.flow2.FlowGraph;
+import org.opendc.simulator.flow2.FlowStage;
+import org.opendc.simulator.flow2.FlowStageLogic;
+import org.opendc.simulator.flow2.InHandler;
+import org.opendc.simulator.flow2.InPort;
+import org.opendc.simulator.flow2.OutPort;
+import org.opendc.simulator.flow2.Outlet;
+
+/**
+ * A collection {@link SimPsu} implementations.
+ */
+public class SimPsuFactories {
+ private SimPsuFactories() {}
+
+ /**
+ * Return a {@link SimPsuFactory} of {@link SimPsu} implementations that do not measure any power consumption.
+ *
+ * <p>
+ * This implementation has the lowest performance impact and users are advised to use this factory if they do not
+ * consider power consumption in their experiments.
+ */
+ public static SimPsuFactory noop() {
+ return NoopPsu.FACTORY;
+ }
+
+ /**
+ * Return a {@link SimPsuFactory} of {@link SimPsu} implementations that use a {@link CpuPowerModel} to estimate the
+ * power consumption of a machine based on its CPU utilization.
+ *
+ * @param model The power model to estimate the power consumption based on the CPU usage.
+ */
+ public static SimPsuFactory simple(CpuPowerModel model) {
+ return (machine, graph) -> new SimplePsu(graph, model);
+ }
+
+ /**
+ * A {@link SimPsu} implementation that does not attempt to measure power consumption.
+ */
+ private static final class NoopPsu extends SimPsu implements FlowStageLogic {
+ private static final SimPsuFactory FACTORY = (machine, graph) -> new NoopPsu(graph);
+
+ private final FlowStage stage;
+ private final OutPort out;
+
+ NoopPsu(FlowGraph graph) {
+ stage = graph.newStage(this);
+ out = stage.getOutlet("out");
+ out.setMask(true);
+ }
+
+ @Override
+ public double getPowerDemand() {
+ return 0;
+ }
+
+ @Override
+ public double getPowerUsage() {
+ return 0;
+ }
+
+ @Override
+ public double getEnergyUsage() {
+ return 0;
+ }
+
+ @Override
+ InPort getCpuPower(int id, ProcessingUnit model) {
+ final InPort port = stage.getInlet("cpu" + id);
+ port.setMask(true);
+ return port;
+ }
+
+ @Override
+ public long onUpdate(FlowStage ctx, long now) {
+ return Long.MAX_VALUE;
+ }
+
+ @NotNull
+ @Override
+ public Outlet getFlowOutlet() {
+ return out;
+ }
+ }
+
+ /**
+ * A {@link SimPsu} implementation that estimates the power consumption based on CPU usage.
+ */
+ private static final class SimplePsu extends SimPsu implements FlowStageLogic {
+ private final FlowStage stage;
+ private final OutPort out;
+ private final CpuPowerModel model;
+ private final Clock clock;
+
+ private double targetFreq;
+ private double totalUsage;
+ private long lastUpdate;
+
+ private double powerUsage;
+ private double energyUsage;
+
+ private final InHandler handler = new InHandler() {
+ @Override
+ public void onPush(InPort port, float demand) {
+ totalUsage += -port.getDemand() + demand;
+ }
+
+ @Override
+ public void onUpstreamFinish(InPort port, Throwable cause) {
+ totalUsage -= port.getDemand();
+ }
+ };
+
+ SimplePsu(FlowGraph graph, CpuPowerModel model) {
+ this.stage = graph.newStage(this);
+ this.model = model;
+ this.clock = graph.getEngine().getClock();
+ this.out = stage.getOutlet("out");
+ this.out.setMask(true);
+
+ lastUpdate = graph.getEngine().getClock().millis();
+ }
+
+ @Override
+ public double getPowerDemand() {
+ return totalUsage;
+ }
+
+ @Override
+ public double getPowerUsage() {
+ return powerUsage;
+ }
+
+ @Override
+ public double getEnergyUsage() {
+ updateEnergyUsage(clock.millis());
+ return energyUsage;
+ }
+
+ @Override
+ InPort getCpuPower(int id, ProcessingUnit model) {
+ targetFreq += model.getFrequency();
+
+ final InPort port = stage.getInlet("cpu" + id);
+ port.setHandler(handler);
+ return port;
+ }
+
+ @Override
+ void setCpuFrequency(InPort port, double capacity) {
+ targetFreq += -port.getCapacity() + capacity;
+
+ super.setCpuFrequency(port, capacity);
+ }
+
+ @Override
+ public long onUpdate(FlowStage ctx, long now) {
+ updateEnergyUsage(now);
+
+ double usage = model.computePower(totalUsage / targetFreq);
+ out.push((float) usage);
+ powerUsage = usage;
+
+ return Long.MAX_VALUE;
+ }
+
+ @NotNull
+ @Override
+ public Outlet getFlowOutlet() {
+ return out;
+ }
+
+ /**
+ * Calculate the energy usage up until <code>now</code>.
+ */
+ private void updateEnergyUsage(long now) {
+ long lastUpdate = this.lastUpdate;
+ this.lastUpdate = now;
+
+ long duration = now - lastUpdate;
+ if (duration > 0) {
+ // Compute the energy usage of the machine
+ energyUsage += powerUsage * duration * 0.001;
+ }
+ }
+ }
+}
diff --git a/opendc-simulator/opendc-simulator-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 <code>machine</code>.
+ *
+ * @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}.
+ * <p>
+ * This interface represents the physical view of the peripheral and should be used to configure the physical properties
+ * of the peripheral.
+ */
+public interface SimPeripheral {}
diff --git a/opendc-simulator/opendc-simulator-compute/src/main/java/org/opendc/simulator/compute/kernel/SimHypervisor.java b/opendc-simulator/opendc-simulator-compute/src/main/java/org/opendc/simulator/compute/kernel/SimHypervisor.java
new file mode 100644
index 00000000..6e295837
--- /dev/null
+++ b/opendc-simulator/opendc-simulator-compute/src/main/java/org/opendc/simulator/compute/kernel/SimHypervisor.java
@@ -0,0 +1,911 @@
+/*
+ * Copyright (c) 2022 AtLarge Research
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
+ * of this software and associated documentation files (the "Software"), to deal
+ * in the Software without restriction, including without limitation the rights
+ * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+ * copies of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included in all
+ * copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
+ * SOFTWARE.
+ */
+
+package org.opendc.simulator.compute.kernel;
+
+import java.time.Clock;
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.List;
+import java.util.Map;
+import java.util.SplittableRandom;
+import java.util.stream.Collectors;
+import org.opendc.simulator.compute.SimAbstractMachine;
+import org.opendc.simulator.compute.SimMachine;
+import org.opendc.simulator.compute.SimMachineContext;
+import org.opendc.simulator.compute.SimMemory;
+import org.opendc.simulator.compute.SimNetworkInterface;
+import org.opendc.simulator.compute.SimProcessingUnit;
+import org.opendc.simulator.compute.SimStorageInterface;
+import org.opendc.simulator.compute.device.SimPeripheral;
+import org.opendc.simulator.compute.kernel.cpufreq.ScalingGovernor;
+import org.opendc.simulator.compute.kernel.cpufreq.ScalingGovernorFactory;
+import org.opendc.simulator.compute.kernel.cpufreq.ScalingPolicy;
+import org.opendc.simulator.compute.kernel.interference.VmInterferenceDomain;
+import org.opendc.simulator.compute.kernel.interference.VmInterferenceMember;
+import org.opendc.simulator.compute.kernel.interference.VmInterferenceProfile;
+import org.opendc.simulator.compute.model.MachineModel;
+import org.opendc.simulator.compute.model.ProcessingUnit;
+import org.opendc.simulator.compute.workload.SimWorkload;
+import org.opendc.simulator.flow2.FlowGraph;
+import org.opendc.simulator.flow2.FlowStage;
+import org.opendc.simulator.flow2.FlowStageLogic;
+import org.opendc.simulator.flow2.InHandler;
+import org.opendc.simulator.flow2.InPort;
+import org.opendc.simulator.flow2.Inlet;
+import org.opendc.simulator.flow2.OutHandler;
+import org.opendc.simulator.flow2.OutPort;
+import org.opendc.simulator.flow2.mux.FlowMultiplexer;
+import org.opendc.simulator.flow2.mux.FlowMultiplexerFactory;
+
+/**
+ * A SimHypervisor facilitates the execution of multiple concurrent {@link SimWorkload}s, while acting as a single
+ * workload to another {@link SimMachine}.
+ */
+public final class SimHypervisor implements SimWorkload {
+ private final FlowMultiplexerFactory muxFactory;
+ private final SplittableRandom random;
+ private final ScalingGovernorFactory scalingGovernorFactory;
+ private final VmInterferenceDomain interferenceDomain;
+
+ private Context activeContext;
+ private final ArrayList<VirtualMachine> vms = new ArrayList<>();
+ private final HvCounters counters = new HvCounters();
+
+ /**
+ * Construct a {@link SimHypervisor} instance.
+ *
+ * @param muxFactory The factory for the {@link FlowMultiplexer} to multiplex the workloads.
+ * @param random A randomness generator for the interference calculations.
+ * @param scalingGovernorFactory The factory for the scaling governor to use for scaling the CPU frequency.
+ * @param interferenceDomain The interference domain to which the hypervisor belongs.
+ */
+ private SimHypervisor(
+ FlowMultiplexerFactory muxFactory,
+ SplittableRandom random,
+ ScalingGovernorFactory scalingGovernorFactory,
+ VmInterferenceDomain interferenceDomain) {
+ this.muxFactory = muxFactory;
+ this.random = random;
+ this.scalingGovernorFactory = scalingGovernorFactory;
+ this.interferenceDomain = interferenceDomain;
+ }
+
+ /**
+ * Create a {@link SimHypervisor} instance.
+ *
+ * @param muxFactory The factory for the {@link FlowMultiplexer} to multiplex the workloads.
+ * @param random A randomness generator for the interference calculations.
+ * @param scalingGovernorFactory The factory for the scaling governor to use for scaling the CPU frequency.
+ * @param interferenceDomain The interference domain to which the hypervisor belongs.
+ */
+ public static SimHypervisor create(
+ FlowMultiplexerFactory muxFactory,
+ SplittableRandom random,
+ ScalingGovernorFactory scalingGovernorFactory,
+ VmInterferenceDomain interferenceDomain) {
+ return new SimHypervisor(muxFactory, random, scalingGovernorFactory, interferenceDomain);
+ }
+
+ /**
+ * Create a {@link SimHypervisor} instance with a default interference domain.
+ *
+ * @param muxFactory The factory for the {@link FlowMultiplexer} to multiplex the workloads.
+ * @param random A randomness generator for the interference calculations.
+ * @param scalingGovernorFactory The factory for the scaling governor to use for scaling the CPU frequency.
+ */
+ public static SimHypervisor create(
+ FlowMultiplexerFactory muxFactory, SplittableRandom random, ScalingGovernorFactory scalingGovernorFactory) {
+ return create(muxFactory, random, scalingGovernorFactory, new VmInterferenceDomain());
+ }
+
+ /**
+ * Create a {@link SimHypervisor} instance with a default interference domain and scaling governor.
+ *
+ * @param muxFactory The factory for the {@link FlowMultiplexer} to multiplex the workloads.
+ * @param random A randomness generator for the interference calculations.
+ */
+ public static SimHypervisor create(FlowMultiplexerFactory muxFactory, SplittableRandom random) {
+ return create(muxFactory, random, null);
+ }
+
+ /**
+ * Return the performance counters of the hypervisor.
+ */
+ public SimHypervisorCounters getCounters() {
+ return counters;
+ }
+
+ /**
+ * Return the virtual machines running on this hypervisor.
+ */
+ public List<? extends SimVirtualMachine> getVirtualMachines() {
+ return Collections.unmodifiableList(vms);
+ }
+
+ /**
+ * Create a {@link SimVirtualMachine} instance on which users may run a [SimWorkload].
+ *
+ * @param model The machine to create.
+ */
+ public SimVirtualMachine newMachine(MachineModel model) {
+ if (!canFit(model)) {
+ throw new IllegalArgumentException("Machine does not fit");
+ }
+
+ VirtualMachine vm = new VirtualMachine(model);
+ vms.add(vm);
+ return vm;
+ }
+
+ /**
+ * Remove the specified <code>machine</code> from the hypervisor.
+ *
+ * @param machine The machine to remove.
+ */
+ public void removeMachine(SimVirtualMachine machine) {
+ if (vms.remove(machine)) {
+ // This cast must always succeed, since `_vms` only contains `VirtualMachine` types.
+ ((VirtualMachine) machine).close();
+ }
+ }
+
+ /**
+ * Return the CPU capacity of the hypervisor in MHz.
+ */
+ public double getCpuCapacity() {
+ final Context context = activeContext;
+
+ if (context == null) {
+ return 0.0;
+ }
+
+ return context.previousCapacity;
+ }
+
+ /**
+ * The CPU demand of the hypervisor in MHz.
+ */
+ public double getCpuDemand() {
+ final Context context = activeContext;
+
+ if (context == null) {
+ return 0.0;
+ }
+
+ return context.previousDemand;
+ }
+
+ /**
+ * The CPU usage of the hypervisor in MHz.
+ */
+ public double getCpuUsage() {
+ final Context context = activeContext;
+
+ if (context == null) {
+ return 0.0;
+ }
+
+ return context.previousRate;
+ }
+
+ /**
+ * Determine whether the specified machine characterized by <code>model</code> can fit on this hypervisor at this
+ * moment.
+ */
+ public boolean canFit(MachineModel model) {
+ final Context context = activeContext;
+ if (context == null) {
+ return false;
+ }
+
+ final FlowMultiplexer multiplexer = context.multiplexer;
+ return (multiplexer.getMaxInputs() - multiplexer.getInputCount())
+ >= model.getCpus().size();
+ }
+
+ @Override
+ public void onStart(SimMachineContext ctx) {
+ final Context context = new Context(ctx, muxFactory, scalingGovernorFactory, counters);
+ context.start();
+ activeContext = context;
+ }
+
+ @Override
+ public void onStop(SimMachineContext ctx) {
+ final Context context = activeContext;
+ if (context != null) {
+ activeContext = null;
+ context.stop();
+ }
+ }
+
+ /**
+ * The context which carries the state when the hypervisor is running on a machine.
+ */
+ private static final class Context implements FlowStageLogic {
+ private final SimMachineContext ctx;
+ private final FlowMultiplexer multiplexer;
+ private final FlowStage stage;
+ private final List<ScalingGovernor> scalingGovernors;
+ private final Clock clock;
+ private final HvCounters counters;
+
+ private long lastCounterUpdate;
+ private final double d;
+ private float previousDemand;
+ private float previousRate;
+ private float previousCapacity;
+
+ private Context(
+ SimMachineContext ctx,
+ FlowMultiplexerFactory muxFactory,
+ ScalingGovernorFactory scalingGovernorFactory,
+ HvCounters counters) {
+
+ this.ctx = ctx;
+ this.counters = counters;
+
+ final FlowGraph graph = ctx.getGraph();
+ this.multiplexer = muxFactory.newMultiplexer(graph);
+ this.stage = graph.newStage(this);
+ this.clock = graph.getEngine().getClock();
+
+ this.lastCounterUpdate = clock.millis();
+
+ if (scalingGovernorFactory != null) {
+ this.scalingGovernors = ctx.getCpus().stream()
+ .map(cpu -> scalingGovernorFactory.newGovernor(new ScalingPolicyImpl(cpu)))
+ .collect(Collectors.toList());
+ } else {
+ this.scalingGovernors = Collections.emptyList();
+ }
+
+ float cpuCapacity = 0.f;
+ final List<? extends SimProcessingUnit> cpus = ctx.getCpus();
+ for (SimProcessingUnit cpu : cpus) {
+ cpuCapacity += cpu.getFrequency();
+ }
+ this.d = cpus.size() / cpuCapacity;
+ }
+
+ /**
+ * Start the hypervisor on a new machine.
+ */
+ void start() {
+ final FlowGraph graph = ctx.getGraph();
+ final FlowMultiplexer multiplexer = this.multiplexer;
+
+ for (SimProcessingUnit cpu : ctx.getCpus()) {
+ graph.connect(multiplexer.newOutput(), cpu.getInput());
+ }
+
+ for (ScalingGovernor governor : scalingGovernors) {
+ governor.onStart();
+ }
+ }
+
+ /**
+ * Stop the hypervisor.
+ */
+ void stop() {
+ // Synchronize the counters before stopping the hypervisor. Otherwise, the last report is missed.
+ updateCounters(clock.millis());
+
+ stage.close();
+ }
+
+ /**
+ * Invalidate the {@link FlowStage} of the hypervisor.
+ */
+ void invalidate() {
+ stage.invalidate();
+ }
+
+ /**
+ * Update the performance counters of the hypervisor.
+ *
+ * @param now The timestamp at which to update the counter.
+ */
+ void updateCounters(long now) {
+ long lastUpdate = this.lastCounterUpdate;
+ this.lastCounterUpdate = now;
+ long delta = now - lastUpdate;
+
+ if (delta > 0) {
+ final HvCounters counters = this.counters;
+
+ float demand = previousDemand;
+ float rate = previousRate;
+ float capacity = previousCapacity;
+
+ final double factor = this.d * delta;
+
+ counters.cpuActiveTime += Math.round(rate * factor);
+ counters.cpuIdleTime += Math.round((capacity - rate) * factor);
+ counters.cpuStealTime += Math.round((demand - rate) * factor);
+ }
+ }
+
+ /**
+ * Update the performance counters of the hypervisor.
+ */
+ void updateCounters() {
+ updateCounters(clock.millis());
+ }
+
+ @Override
+ public long onUpdate(FlowStage ctx, long now) {
+ updateCounters(now);
+
+ final FlowMultiplexer multiplexer = this.multiplexer;
+ final List<ScalingGovernor> scalingGovernors = this.scalingGovernors;
+
+ float demand = multiplexer.getDemand();
+ float rate = multiplexer.getRate();
+ float capacity = multiplexer.getCapacity();
+
+ this.previousDemand = demand;
+ this.previousRate = rate;
+ this.previousCapacity = capacity;
+
+ double load = rate / Math.min(1.0, capacity);
+
+ if (!scalingGovernors.isEmpty()) {
+ for (ScalingGovernor governor : scalingGovernors) {
+ governor.onLimit(load);
+ }
+ }
+
+ return Long.MAX_VALUE;
+ }
+ }
+
+ /**
+ * A {@link ScalingPolicy} for a physical CPU of the hypervisor.
+ */
+ private static final class ScalingPolicyImpl implements ScalingPolicy {
+ private final SimProcessingUnit cpu;
+
+ private ScalingPolicyImpl(SimProcessingUnit cpu) {
+ this.cpu = cpu;
+ }
+
+ @Override
+ public SimProcessingUnit getCpu() {
+ return cpu;
+ }
+
+ @Override
+ public double getTarget() {
+ return cpu.getFrequency();
+ }
+
+ @Override
+ public void setTarget(double target) {
+ cpu.setFrequency(target);
+ }
+
+ @Override
+ public double getMin() {
+ return 0;
+ }
+
+ @Override
+ public double getMax() {
+ return cpu.getModel().getFrequency();
+ }
+ }
+
+ /**
+ * A virtual machine running on the hypervisor.
+ */
+ private class VirtualMachine extends SimAbstractMachine implements SimVirtualMachine {
+ private boolean isClosed;
+ private final VmCounters counters = new VmCounters(this);
+
+ private VirtualMachine(MachineModel model) {
+ super(model);
+ }
+
+ @Override
+ public SimHypervisorCounters getCounters() {
+ return counters;
+ }
+
+ @Override
+ public double getCpuDemand() {
+ final VmContext context = (VmContext) getActiveContext();
+
+ if (context == null) {
+ return 0.0;
+ }
+
+ return context.previousDemand;
+ }
+
+ @Override
+ public double getCpuUsage() {
+ final VmContext context = (VmContext) getActiveContext();
+
+ if (context == null) {
+ return 0.0;
+ }
+
+ return context.usage;
+ }
+
+ @Override
+ public double getCpuCapacity() {
+ final VmContext context = (VmContext) getActiveContext();
+
+ if (context == null) {
+ return 0.0;
+ }
+
+ return context.previousCapacity;
+ }
+
+ @Override
+ public List<? extends SimPeripheral> getPeripherals() {
+ return Collections.emptyList();
+ }
+
+ @Override
+ protected Context createContext(SimWorkload workload, Map<String, Object> meta) {
+ if (isClosed) {
+ throw new IllegalStateException("Virtual machine does not exist anymore");
+ }
+
+ final SimHypervisor.Context context = activeContext;
+ if (context == null) {
+ throw new IllegalStateException("Hypervisor is inactive");
+ }
+
+ return new VmContext(
+ context, this, random, interferenceDomain, counters, SimHypervisor.this.counters, workload, meta);
+ }
+
+ @Override
+ public Context getActiveContext() {
+ return super.getActiveContext();
+ }
+
+ void close() {
+ if (isClosed) {
+ return;
+ }
+
+ isClosed = true;
+ cancel();
+ }
+ }
+
+ /**
+ * A {@link SimAbstractMachine.Context} for a virtual machine instance.
+ */
+ private static final class VmContext extends SimAbstractMachine.Context implements FlowStageLogic {
+ private final Context context;
+ private final SplittableRandom random;
+ private final VmCounters vmCounters;
+ private final HvCounters hvCounters;
+ private final VmInterferenceMember interferenceMember;
+ private final FlowStage stage;
+ private final FlowMultiplexer multiplexer;
+ private final Clock clock;
+
+ private final List<VCpu> cpus;
+ private final SimAbstractMachine.Memory memory;
+ private final List<SimAbstractMachine.NetworkAdapter> net;
+ private final List<SimAbstractMachine.StorageDevice> disk;
+
+ private final Inlet[] muxInlets;
+ private long lastUpdate;
+ private long lastCounterUpdate;
+ private final double d;
+
+ private float demand;
+ private float usage;
+ private float capacity;
+
+ private float previousDemand;
+ private float previousCapacity;
+
+ private VmContext(
+ Context context,
+ VirtualMachine machine,
+ SplittableRandom random,
+ VmInterferenceDomain interferenceDomain,
+ VmCounters vmCounters,
+ HvCounters hvCounters,
+ SimWorkload workload,
+ Map<String, Object> meta) {
+ super(machine, workload, meta);
+
+ this.context = context;
+ this.random = random;
+ this.vmCounters = vmCounters;
+ this.hvCounters = hvCounters;
+ this.clock = context.clock;
+
+ final VmInterferenceProfile interferenceProfile = (VmInterferenceProfile) meta.get("interference-profile");
+ VmInterferenceMember interferenceMember = null;
+ if (interferenceDomain != null && interferenceProfile != null) {
+ interferenceMember = interferenceDomain.join(interferenceProfile);
+ interferenceMember.activate();
+ }
+ this.interferenceMember = interferenceMember;
+
+ final FlowGraph graph = context.ctx.getGraph();
+ final FlowStage stage = graph.newStage(this);
+ this.stage = stage;
+ this.lastUpdate = clock.millis();
+ this.lastCounterUpdate = clock.millis();
+
+ final FlowMultiplexer multiplexer = context.multiplexer;
+ this.multiplexer = multiplexer;
+
+ final MachineModel model = machine.getModel();
+ final List<ProcessingUnit> cpuModels = model.getCpus();
+ final Inlet[] muxInlets = new Inlet[cpuModels.size()];
+ final ArrayList<VCpu> cpus = new ArrayList<>();
+
+ this.muxInlets = muxInlets;
+ this.cpus = cpus;
+
+ float capacity = 0.f;
+
+ for (int i = 0; i < cpuModels.size(); i++) {
+ final Inlet muxInlet = multiplexer.newInput();
+ muxInlets[i] = muxInlet;
+
+ final InPort input = stage.getInlet("cpu" + i);
+ final OutPort output = stage.getOutlet("mux" + i);
+
+ final Handler handler = new Handler(this, input, output);
+ input.setHandler(handler);
+ output.setHandler(handler);
+
+ final ProcessingUnit cpuModel = cpuModels.get(i);
+ capacity += cpuModel.getFrequency();
+
+ final VCpu cpu = new VCpu(cpuModel, input);
+ cpus.add(cpu);
+
+ graph.connect(output, muxInlet);
+ }
+ this.d = cpuModels.size() / capacity;
+
+ this.memory = new SimAbstractMachine.Memory(graph, model.getMemory());
+
+ int netIndex = 0;
+ final ArrayList<SimAbstractMachine.NetworkAdapter> net = new ArrayList<>();
+ this.net = net;
+ for (org.opendc.simulator.compute.model.NetworkAdapter adapter : model.getNetwork()) {
+ net.add(new SimAbstractMachine.NetworkAdapter(graph, adapter, netIndex++));
+ }
+
+ int diskIndex = 0;
+ final ArrayList<SimAbstractMachine.StorageDevice> disk = new ArrayList<>();
+ this.disk = disk;
+ for (org.opendc.simulator.compute.model.StorageDevice device : model.getStorage()) {
+ disk.add(new SimAbstractMachine.StorageDevice(graph, device, diskIndex++));
+ }
+ }
+
+ /**
+ * Update the performance counters of the virtual machine.
+ *
+ * @param now The timestamp at which to update the counter.
+ */
+ void updateCounters(long now) {
+ long lastUpdate = this.lastCounterUpdate;
+ this.lastCounterUpdate = now;
+ long delta = now - lastUpdate;
+
+ if (delta > 0) {
+ final VmCounters counters = this.vmCounters;
+
+ float demand = this.previousDemand;
+ float rate = this.usage;
+ float capacity = this.previousCapacity;
+
+ final double factor = this.d * delta;
+ final double active = rate * factor;
+
+ counters.cpuActiveTime += Math.round(active);
+ counters.cpuIdleTime += Math.round((capacity - rate) * factor);
+ counters.cpuStealTime += Math.round((demand - rate) * factor);
+ }
+ }
+
+ /**
+ * Update the performance counters of the virtual machine.
+ */
+ void updateCounters() {
+ updateCounters(clock.millis());
+ }
+
+ @Override
+ public FlowGraph getGraph() {
+ return stage.getGraph();
+ }
+
+ @Override
+ public List<? extends SimProcessingUnit> getCpus() {
+ return cpus;
+ }
+
+ @Override
+ public SimMemory getMemory() {
+ return memory;
+ }
+
+ @Override
+ public List<? extends SimNetworkInterface> getNetworkInterfaces() {
+ return net;
+ }
+
+ @Override
+ public List<? extends SimStorageInterface> getStorageInterfaces() {
+ return disk;
+ }
+
+ @Override
+ public long onUpdate(FlowStage ctx, long now) {
+ float usage = 0.f;
+ for (Inlet inlet : muxInlets) {
+ usage += ((InPort) inlet).getRate();
+ }
+ this.usage = usage;
+ this.previousDemand = demand;
+ this.previousCapacity = capacity;
+
+ long lastUpdate = this.lastUpdate;
+ this.lastUpdate = now;
+ long delta = now - lastUpdate;
+
+ if (delta > 0) {
+ final VmInterferenceMember interferenceMember = this.interferenceMember;
+ double penalty = 0.0;
+
+ if (interferenceMember != null) {
+ final FlowMultiplexer multiplexer = this.multiplexer;
+ double load = multiplexer.getRate() / Math.min(1.0, multiplexer.getCapacity());
+ penalty = 1 - interferenceMember.apply(random, load);
+ }
+
+ final double factor = this.d * delta;
+ final long lostTime = Math.round(factor * usage * penalty);
+
+ this.vmCounters.cpuLostTime += lostTime;
+ this.hvCounters.cpuLostTime += lostTime;
+ }
+
+ // Invalidate the FlowStage of the hypervisor to update its counters (via onUpdate)
+ context.invalidate();
+
+ return Long.MAX_VALUE;
+ }
+
+ @Override
+ protected void doCancel() {
+ super.doCancel();
+
+ // Synchronize the counters before stopping the hypervisor. Otherwise, the last report is missed.
+ updateCounters(clock.millis());
+
+ stage.close();
+
+ final FlowMultiplexer multiplexer = this.multiplexer;
+ for (Inlet muxInlet : muxInlets) {
+ multiplexer.releaseInput(muxInlet);
+ }
+
+ final VmInterferenceMember interferenceMember = this.interferenceMember;
+ if (interferenceMember != null) {
+ interferenceMember.deactivate();
+ }
+ }
+ }
+
+ /**
+ * A {@link SimProcessingUnit} of a virtual machine.
+ */
+ private static final class VCpu implements SimProcessingUnit {
+ private final ProcessingUnit model;
+ private final InPort input;
+
+ private VCpu(ProcessingUnit model, InPort input) {
+ this.model = model;
+ this.input = input;
+
+ input.pull((float) model.getFrequency());
+ }
+
+ @Override
+ public double getFrequency() {
+ return input.getCapacity();
+ }
+
+ @Override
+ public void setFrequency(double frequency) {
+ input.pull((float) frequency);
+ }
+
+ @Override
+ public double getDemand() {
+ return input.getDemand();
+ }
+
+ @Override
+ public double getSpeed() {
+ return input.getRate();
+ }
+
+ @Override
+ public ProcessingUnit getModel() {
+ return model;
+ }
+
+ @Override
+ public Inlet getInput() {
+ return input;
+ }
+
+ @Override
+ public String toString() {
+ return "SimHypervisor.VCpu[model" + model + "]";
+ }
+ }
+
+ /**
+ * A handler for forwarding flow between an inlet and outlet.
+ */
+ private static class Handler implements InHandler, OutHandler {
+ private final InPort input;
+ private final OutPort output;
+ private final VmContext context;
+
+ private Handler(VmContext context, InPort input, OutPort output) {
+ this.context = context;
+ this.input = input;
+ this.output = output;
+ }
+
+ @Override
+ public void onPush(InPort port, float demand) {
+ context.demand += -port.getDemand() + demand;
+
+ output.push(demand);
+ }
+
+ @Override
+ public void onUpstreamFinish(InPort port, Throwable cause) {
+ context.demand -= port.getDemand();
+
+ output.push(0.f);
+ }
+
+ @Override
+ public float getRate(InPort port) {
+ return output.getRate();
+ }
+
+ @Override
+ public void onPull(OutPort port, float capacity) {
+ context.capacity += -port.getCapacity() + capacity;
+
+ input.pull(capacity);
+ }
+
+ @Override
+ public void onDownstreamFinish(OutPort port, Throwable cause) {
+ context.capacity -= port.getCapacity();
+
+ input.pull(0.f);
+ }
+ }
+
+ /**
+ * Implementation of {@link SimHypervisorCounters} for the hypervisor.
+ */
+ private class HvCounters implements SimHypervisorCounters {
+ private long cpuActiveTime;
+ private long cpuIdleTime;
+ private long cpuStealTime;
+ private long cpuLostTime;
+
+ @Override
+ public long getCpuActiveTime() {
+ return cpuActiveTime;
+ }
+
+ @Override
+ public long getCpuIdleTime() {
+ return cpuIdleTime;
+ }
+
+ @Override
+ public long getCpuStealTime() {
+ return cpuStealTime;
+ }
+
+ @Override
+ public long getCpuLostTime() {
+ return cpuLostTime;
+ }
+
+ @Override
+ public void sync() {
+ final Context context = activeContext;
+
+ if (context != null) {
+ context.updateCounters();
+ }
+ }
+ }
+
+ /**
+ * Implementation of {@link SimHypervisorCounters} for the virtual machine.
+ */
+ private static class VmCounters implements SimHypervisorCounters {
+ private final VirtualMachine vm;
+ private long cpuActiveTime;
+ private long cpuIdleTime;
+ private long cpuStealTime;
+ private long cpuLostTime;
+
+ private VmCounters(VirtualMachine vm) {
+ this.vm = vm;
+ }
+
+ @Override
+ public long getCpuActiveTime() {
+ return cpuActiveTime;
+ }
+
+ @Override
+ public long getCpuIdleTime() {
+ return cpuIdleTime;
+ }
+
+ @Override
+ public long getCpuStealTime() {
+ return cpuStealTime;
+ }
+
+ @Override
+ public long getCpuLostTime() {
+ return cpuLostTime;
+ }
+
+ @Override
+ public void sync() {
+ final VmContext context = (VmContext) vm.getActiveContext();
+
+ if (context != null) {
+ context.updateCounters();
+ }
+ }
+ }
+}
diff --git a/opendc-simulator/opendc-simulator-compute/src/main/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.
+ *
+ * <p>
+ * Each of the scaling governors implements a single, possibly parametrized, performance scaling algorithm.
+ *
+ * @see <a href="https://www.kernel.org/doc/html/latest/admin-guide/pm/cpufreq.html">documentation of the Linux CPUFreq subsystem</a>.
+ */
+public interface ScalingGovernor {
+ /**
+ * 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 <code>performance</code> scaling governor.
+ *
+ * <p>
+ * This governor causes the highest possible frequency to be requested from the CPUs.
+ */
+ public static ScalingGovernorFactory performance() {
+ return PerformanceScalingGovernor.FACTORY;
+ }
+
+ /**
+ * Return a {@link ScalingGovernorFactory} for the <code>powersave</code> scaling governor.
+ *
+ * <p>
+ * This governor causes the lowest possible frequency to be requested from the CPUs.
+ */
+ public static ScalingGovernorFactory powerSave() {
+ return PowerSaveScalingGovernor.FACTORY;
+ }
+
+ /**
+ * Return a {@link ScalingGovernorFactory} for the <code>conservative</code> scaling governor from the Linux kernel.
+ *
+ * @param threshold The threshold before scaling.
+ * @param stepSize The size of the frequency steps (use negative value for automatic).
+ */
+ public static ScalingGovernorFactory conservative(double threshold, double stepSize) {
+ return (policy) -> new ConservativeScalingGovernor(policy, threshold, stepSize);
+ }
+
+ /**
+ * Return a {@link ScalingGovernorFactory} for the <code>conservative</code> scaling governor from the Linux kernel.
+ *
+ * @param threshold The threshold before scaling.
+ */
+ public static ScalingGovernorFactory conservative(double threshold) {
+ return conservative(threshold, -1.0);
+ }
+
+ /**
+ * Return a {@link ScalingGovernorFactory} for the <code>ondemand</code> scaling governor from the Linux kernel.
+ *
+ * @param threshold The threshold before scaling.
+ */
+ public static ScalingGovernorFactory ondemand(double threshold) {
+ return (policy) -> new OnDemandScalingGovernor(policy, threshold);
+ }
+
+ private abstract static class AbstractScalingGovernor implements ScalingGovernor {
+ protected final ScalingPolicy policy;
+
+ AbstractScalingGovernor(ScalingPolicy policy) {
+ this.policy = policy;
+ }
+ }
+
+ private static class PerformanceScalingGovernor extends AbstractScalingGovernor {
+ static final ScalingGovernorFactory FACTORY = PerformanceScalingGovernor::new;
+
+ private PerformanceScalingGovernor(ScalingPolicy policy) {
+ super(policy);
+ }
+
+ @Override
+ public void onStart() {
+ policy.setTarget(policy.getMax());
+ }
+ }
+
+ private static class PowerSaveScalingGovernor extends AbstractScalingGovernor {
+ static final ScalingGovernorFactory FACTORY = PowerSaveScalingGovernor::new;
+
+ private PowerSaveScalingGovernor(ScalingPolicy policy) {
+ super(policy);
+ }
+
+ @Override
+ public void onStart() {
+ policy.setTarget(policy.getMin());
+ }
+ }
+
+ private static class ConservativeScalingGovernor extends AbstractScalingGovernor {
+ private final double threshold;
+ private final double stepSize;
+ private double previousLoad;
+
+ private ConservativeScalingGovernor(ScalingPolicy policy, double threshold, double stepSize) {
+ super(policy);
+
+ this.threshold = threshold;
+ this.previousLoad = threshold;
+
+ if (stepSize < 0) {
+ // https://github.com/torvalds/linux/blob/master/drivers/cpufreq/cpufreq_conservative.c#L33
+ this.stepSize = policy.getMax() * 0.05;
+ } else {
+ this.stepSize = Math.min(stepSize, policy.getMax());
+ }
+ }
+
+ @Override
+ public void onStart() {
+ policy.setTarget(policy.getMin());
+ }
+
+ @Override
+ public void onLimit(double load) {
+ final ScalingPolicy policy = this.policy;
+ double currentTarget = policy.getTarget();
+ if (load > threshold) {
+ // Check for load increase (see:
+ // https://github.com/torvalds/linux/blob/master/drivers/cpufreq/cpufreq_conservative.c#L102)
+ double step = 0.0;
+
+ if (load > previousLoad) {
+ step = stepSize;
+ } else if (load < previousLoad) {
+ step = -stepSize;
+ }
+
+ double target = Math.min(Math.max(currentTarget + step, policy.getMin()), policy.getMax());
+ policy.setTarget(target);
+ }
+ previousLoad = load;
+ }
+ }
+
+ private static class OnDemandScalingGovernor extends AbstractScalingGovernor {
+ private final double threshold;
+ private final double multiplier;
+
+ private OnDemandScalingGovernor(ScalingPolicy policy, double threshold) {
+ super(policy);
+
+ this.threshold = threshold;
+ this.multiplier = (policy.getMax() - policy.getMin()) / 100;
+ }
+
+ @Override
+ public void onStart() {
+ policy.setTarget(policy.getMin());
+ }
+
+ @Override
+ public void onLimit(double load) {
+ final ScalingPolicy policy = this.policy;
+ double target;
+
+ if (load < threshold) {
+ /* Proportional scaling (see: https://github.com/torvalds/linux/blob/master/drivers/cpufreq/cpufreq_ondemand.c#L151). */
+ target = policy.getMin() + load * multiplier;
+ } else {
+ target = policy.getMax();
+ }
+
+ policy.setTarget(target);
+ }
+ }
+}
diff --git a/opendc-simulator/opendc-simulator-compute/src/main/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<VmInterferenceProfile, VmInterferenceMember> cache = new WeakHashMap<>();
+
+ /**
+ * The set of members active in this domain.
+ */
+ private final ArrayList<VmInterferenceMember> activeKeys = new ArrayList<>();
+
+ /**
+ * Queue of participants that will be removed or added to the active groups.
+ */
+ private final ArrayDeque<VmInterferenceMember> participants = new ArrayDeque<>();
+
+ /**
+ * Join this interference domain with the specified <code>profile</code> and return the {@link VmInterferenceMember}
+ * associated with the profile. If the member does not exist, it will be created.
+ */
+ public VmInterferenceMember join(VmInterferenceProfile profile) {
+ return cache.computeIfAbsent(profile, (key) -> key.newMember(this));
+ }
+
+ /**
+ * Mark the specified <code>member</code> as active in this interference domain.
+ */
+ void activate(VmInterferenceMember member) {
+ final ArrayList<VmInterferenceMember> activeKeys = this.activeKeys;
+ int pos = Collections.binarySearch(activeKeys, member);
+ if (pos < 0) {
+ activeKeys.add(-pos - 1, member);
+ }
+
+ computeActiveGroups(activeKeys, member);
+ }
+
+ /**
+ * Mark the specified <code>member</code> as inactive in this interference domain.
+ */
+ void deactivate(VmInterferenceMember member) {
+ final ArrayList<VmInterferenceMember> activeKeys = this.activeKeys;
+ activeKeys.remove(member);
+ computeActiveGroups(activeKeys, member);
+ }
+
+ /**
+ * (Re-)compute the active groups.
+ */
+ private void computeActiveGroups(ArrayList<VmInterferenceMember> activeKeys, VmInterferenceMember member) {
+ if (activeKeys.isEmpty()) {
+ return;
+ }
+
+ final int[] groups = member.membership;
+ final int[][] members = member.members;
+ final ArrayDeque<VmInterferenceMember> participants = this.participants;
+
+ for (int group : groups) {
+ int[] groupMembers = members[group];
+
+ int i = 0;
+ int j = 0;
+ int intersection = 0;
+
+ // Compute the intersection of the group members and the current active members
+ while (i < groupMembers.length && j < activeKeys.size()) {
+ int l = groupMembers[i];
+ final VmInterferenceMember rightEntry = activeKeys.get(j);
+ int r = rightEntry.id;
+
+ if (l < r) {
+ i++;
+ } else if (l > r) {
+ j++;
+ } else {
+ if (++intersection > 1) {
+ rightEntry.addGroup(group);
+ } else {
+ participants.add(rightEntry);
+ }
+
+ i++;
+ j++;
+ }
+ }
+
+ while (true) {
+ VmInterferenceMember participant = participants.poll();
+
+ if (participant == null) {
+ break;
+ }
+
+ if (intersection <= 1) {
+ participant.removeGroup(group);
+ } else {
+ participant.addGroup(group);
+ }
+ }
+ }
+ }
+}
diff --git a/opendc-simulator/opendc-simulator-compute/src/main/java/org/opendc/simulator/compute/kernel/interference/VmInterferenceMember.java b/opendc-simulator/opendc-simulator-compute/src/main/java/org/opendc/simulator/compute/kernel/interference/VmInterferenceMember.java
new file mode 100644
index 00000000..64cd5077
--- /dev/null
+++ b/opendc-simulator/opendc-simulator-compute/src/main/java/org/opendc/simulator/compute/kernel/interference/VmInterferenceMember.java
@@ -0,0 +1,177 @@
+/*
+ * Copyright (c) 2022 AtLarge Research
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
+ * of this software and associated documentation files (the "Software"), to deal
+ * in the Software without restriction, including without limitation the rights
+ * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+ * copies of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included in all
+ * copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
+ * SOFTWARE.
+ */
+
+package org.opendc.simulator.compute.kernel.interference;
+
+import java.util.Arrays;
+import java.util.SplittableRandom;
+import org.jetbrains.annotations.NotNull;
+
+/**
+ * A participant of an interference domain.
+ */
+public final class VmInterferenceMember implements Comparable<VmInterferenceMember> {
+ private final VmInterferenceDomain domain;
+ private final VmInterferenceModel model;
+ final int id;
+ final int[] membership;
+ final int[][] members;
+ private final double[] targets;
+ private final double[] scores;
+
+ private int[] groups = new int[2];
+ private int groupsSize = 0;
+
+ private int refCount = 0;
+
+ VmInterferenceMember(
+ VmInterferenceDomain domain,
+ VmInterferenceModel model,
+ int id,
+ int[] membership,
+ int[][] members,
+ double[] targets,
+ double[] scores) {
+ this.domain = domain;
+ this.model = model;
+ this.id = id;
+ this.membership = membership;
+ this.members = members;
+ this.targets = targets;
+ this.scores = scores;
+ }
+
+ /**
+ * Mark this member as active in this interference domain.
+ */
+ public void activate() {
+ if (refCount++ <= 0) {
+ domain.activate(this);
+ }
+ }
+
+ /**
+ * Mark this member as inactive in this interference domain.
+ */
+ public void deactivate() {
+ if (--refCount <= 0) {
+ domain.deactivate(this);
+ }
+ }
+
+ /**
+ * Compute the performance score of the member in this interference domain.
+ *
+ * @param random The source of randomness to apply when computing the performance score.
+ * @param load The overall load on the interference domain.
+ * @return A score representing the performance score to be applied to the member, with 1
+ * meaning no influence, <1 means that performance degrades, and >1 means that performance improves.
+ */
+ public double apply(SplittableRandom random, double load) {
+ int groupsSize = this.groupsSize;
+
+ if (groupsSize == 0) {
+ return 1.0;
+ }
+
+ int[] groups = this.groups;
+ double[] targets = this.targets;
+
+ int low = 0;
+ int high = groupsSize - 1;
+ int group = -1;
+
+ // Perform binary search over the groups based on target load
+ while (low <= high) {
+ int mid = low + high >>> 1;
+ int midGroup = groups[mid];
+ double target = targets[midGroup];
+
+ if (target < load) {
+ low = mid + 1;
+ group = midGroup;
+ } else if (target > load) {
+ high = mid - 1;
+ } else {
+ group = midGroup;
+ break;
+ }
+ }
+
+ if (group >= 0 && random.nextInt(members[group].length) == 0) {
+ return scores[group];
+ }
+
+ return 1.0;
+ }
+
+ /**
+ * Add an active group to this member.
+ */
+ void addGroup(int group) {
+ int[] groups = this.groups;
+ int groupsSize = this.groupsSize;
+ int pos = Arrays.binarySearch(groups, 0, groupsSize, group);
+
+ if (pos >= 0) {
+ return;
+ }
+
+ int idx = -pos - 1;
+
+ if (groups.length == groupsSize) {
+ int newSize = groupsSize + (groupsSize >> 1);
+ groups = Arrays.copyOf(groups, newSize);
+ this.groups = groups;
+ }
+
+ System.arraycopy(groups, idx, groups, idx + 1, groupsSize - idx);
+ groups[idx] = group;
+ this.groupsSize += 1;
+ }
+
+ /**
+ * Remove an active group from this member.
+ */
+ void removeGroup(int group) {
+ int[] groups = this.groups;
+ int groupsSize = this.groupsSize;
+ int pos = Arrays.binarySearch(groups, 0, groupsSize, group);
+
+ if (pos < 0) {
+ return;
+ }
+
+ System.arraycopy(groups, pos + 1, groups, pos, groupsSize - pos - 1);
+ this.groupsSize -= 1;
+ }
+
+ @Override
+ public int compareTo(@NotNull VmInterferenceMember member) {
+ int cmp = Integer.compare(model.hashCode(), member.model.hashCode());
+ if (cmp != 0) {
+ return cmp;
+ }
+
+ return Integer.compare(id, member.id);
+ }
+}
diff --git a/opendc-simulator/opendc-simulator-compute/src/main/java/org/opendc/simulator/compute/kernel/interference/VmInterferenceModel.java b/opendc-simulator/opendc-simulator-compute/src/main/java/org/opendc/simulator/compute/kernel/interference/VmInterferenceModel.java
new file mode 100644
index 00000000..e2093266
--- /dev/null
+++ b/opendc-simulator/opendc-simulator-compute/src/main/java/org/opendc/simulator/compute/kernel/interference/VmInterferenceModel.java
@@ -0,0 +1,185 @@
+/*
+ * Copyright (c) 2022 AtLarge Research
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
+ * of this software and associated documentation files (the "Software"), to deal
+ * in the Software without restriction, including without limitation the rights
+ * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+ * copies of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included in all
+ * copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
+ * SOFTWARE.
+ */
+
+package org.opendc.simulator.compute.kernel.interference;
+
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.Comparator;
+import java.util.HashMap;
+import java.util.Map;
+import java.util.Set;
+import java.util.TreeMap;
+import java.util.TreeSet;
+import org.jetbrains.annotations.Nullable;
+
+/**
+ * An interference model that models the resource interference between virtual machines on a host.
+ */
+public final class VmInterferenceModel {
+ private final Map<String, Integer> idMapping;
+ private final int[][] members;
+ private final int[][] membership;
+ private final double[] targets;
+ private final double[] scores;
+
+ private VmInterferenceModel(
+ Map<String, Integer> idMapping, int[][] members, int[][] membership, double[] targets, double[] scores) {
+ this.idMapping = idMapping;
+ this.members = members;
+ this.membership = membership;
+ this.targets = targets;
+ this.scores = scores;
+ }
+
+ /**
+ * Create a {@link Builder} for constructing a {@link VmInterferenceModel}.
+ */
+ public static Builder builder() {
+ return new Builder(256);
+ }
+
+ /**
+ * Return the {@link VmInterferenceProfile} associated with the specified <code>id</code>.
+ *
+ * @param id The identifier of the virtual machine.
+ * @return A {@link VmInterferenceProfile} representing the virtual machine as part of interference model or
+ * <code>null</code> if there is no profile for the virtual machine.
+ */
+ @Nullable
+ public VmInterferenceProfile getProfile(String id) {
+ Integer intId = idMapping.get(id);
+ if (intId == null) {
+ return null;
+ }
+ return new VmInterferenceProfile(this, intId, membership[intId], members, targets, scores);
+ }
+
+ /**
+ * Builder class for a {@link VmInterferenceModel}.
+ */
+ public static final class Builder {
+ private double[] targets;
+ private double[] scores;
+ private final ArrayList<Set<String>> members;
+ private final TreeSet<String> ids;
+ private int size;
+
+ private Builder(int initialCapacity) {
+ this.targets = new double[initialCapacity];
+ this.scores = new double[initialCapacity];
+ this.members = new ArrayList<>(initialCapacity);
+ this.ids = new TreeSet<>();
+ }
+
+ /**
+ * Add the specified group to the model.
+ */
+ public Builder addGroup(Set<String> members, double targetLoad, double score) {
+ int size = this.size;
+
+ if (size == targets.length) {
+ grow();
+ }
+
+ targets[size] = targetLoad;
+ scores[size] = score;
+ this.members.add(members);
+ ids.addAll(members);
+
+ this.size++;
+
+ return this;
+ }
+
+ /**
+ * Build the {@link VmInterferenceModel}.
+ */
+ public VmInterferenceModel build() {
+ int size = this.size;
+ double[] targets = this.targets;
+ double[] scores = this.scores;
+ ArrayList<Set<String>> members = this.members;
+
+ Integer[] indices = new Integer[size];
+ Arrays.setAll(indices, (i) -> i);
+ Arrays.sort(
+ indices,
+ Comparator.comparingDouble((Integer l) -> targets[l])
+ .thenComparingDouble(l -> scores[l])
+ .thenComparingInt(l -> l));
+
+ double[] newTargets = new double[size];
+ double[] newScores = new double[size];
+ int[][] newMembers = new int[size][];
+
+ int nextId = 0;
+
+ Map<String, Integer> idMapping = new HashMap<>();
+ TreeMap<String, ArrayList<Integer>> membership = new TreeMap<>();
+ for (String id : ids) {
+ idMapping.put(id, nextId++);
+ membership.put(id, new ArrayList<>());
+ }
+
+ for (int group = 0; group < indices.length; group++) {
+ int j = indices[group];
+ newTargets[group] = targets[j];
+ newScores[group] = scores[j];
+
+ Set<String> groupMembers = members.get(j);
+ int[] newGroupMembers = new int[groupMembers.size()];
+ int k = 0;
+
+ for (String groupMember : groupMembers) {
+ newGroupMembers[k++] = idMapping.get(groupMember);
+ }
+
+ Arrays.sort(newGroupMembers);
+ newMembers[group] = newGroupMembers;
+
+ for (String member : groupMembers) {
+ membership.get(member).add(group);
+ }
+ }
+
+ int[][] newMembership = new int[membership.size()][];
+ int k = 0;
+ for (ArrayList<Integer> value : membership.values()) {
+ newMembership[k++] = value.stream().mapToInt(i -> i).toArray();
+ }
+
+ return new VmInterferenceModel(idMapping, newMembers, newMembership, newTargets, newScores);
+ }
+
+ /**
+ * Helper function to grow the capacity of the internal arrays.
+ */
+ private void grow() {
+ int oldSize = targets.length;
+ int newSize = oldSize + (oldSize >> 1);
+
+ targets = Arrays.copyOf(targets, newSize);
+ scores = Arrays.copyOf(scores, newSize);
+ }
+ }
+}
diff --git a/opendc-simulator/opendc-simulator-compute/src/main/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 <code>domain</code>.
+ */
+ VmInterferenceMember newMember(VmInterferenceDomain domain) {
+ return new VmInterferenceMember(domain, model, id, membership, members, targets, scores);
+ }
+
+ @Override
+ public String toString() {
+ return "VmInterferenceProfile[id=" + id + "]";
+ }
+}
diff --git a/opendc-simulator/opendc-simulator-compute/src/main/java/org/opendc/simulator/compute/model/MachineModel.java b/opendc-simulator/opendc-simulator-compute/src/main/java/org/opendc/simulator/compute/model/MachineModel.java
new file mode 100644
index 00000000..2c625fce
--- /dev/null
+++ b/opendc-simulator/opendc-simulator-compute/src/main/java/org/opendc/simulator/compute/model/MachineModel.java
@@ -0,0 +1,149 @@
+/*
+ * Copyright (c) 2022 AtLarge Research
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
+ * of this software and associated documentation files (the "Software"), to deal
+ * in the Software without restriction, including without limitation the rights
+ * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+ * copies of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included in all
+ * copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
+ * SOFTWARE.
+ */
+
+package org.opendc.simulator.compute.model;
+
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.List;
+import java.util.Objects;
+
+/**
+ * A description of the physical or virtual machine on which a bootable image runs.
+ */
+public final class MachineModel {
+ private final List<ProcessingUnit> cpus;
+ private final List<MemoryUnit> memory;
+ private final List<NetworkAdapter> net;
+ private final List<StorageDevice> storage;
+
+ /**
+ * Construct a {@link MachineModel} instance.
+ *
+ * @param cpus The list of processing units available to the image.
+ * @param memory The list of memory units available to the image.
+ * @param net A list of network adapters available to the machine.
+ * @param storage A list of storage devices available to the machine.
+ */
+ public MachineModel(
+ Iterable<ProcessingUnit> cpus,
+ Iterable<MemoryUnit> memory,
+ Iterable<NetworkAdapter> net,
+ Iterable<StorageDevice> storage) {
+ this.cpus = new ArrayList<>();
+ cpus.forEach(this.cpus::add);
+
+ this.memory = new ArrayList<>();
+ memory.forEach(this.memory::add);
+
+ this.net = new ArrayList<>();
+ net.forEach(this.net::add);
+
+ this.storage = new ArrayList<>();
+ storage.forEach(this.storage::add);
+ }
+
+ /**
+ * Construct a {@link MachineModel} instance.
+ *
+ * @param cpus The list of processing units available to the image.
+ * @param memory The list of memory units available to the image.
+ */
+ public MachineModel(Iterable<ProcessingUnit> cpus, Iterable<MemoryUnit> memory) {
+ this(cpus, memory, Collections.emptyList(), Collections.emptyList());
+ }
+
+ /**
+ * Optimize the [MachineModel] by merging all resources of the same type into a single resource with the combined
+ * capacity. Such configurations can be simulated more efficiently by OpenDC.
+ */
+ public MachineModel optimize() {
+ ProcessingUnit originalCpu = cpus.get(0);
+
+ double freq = 0.0;
+ for (ProcessingUnit cpu : cpus) {
+ freq += cpu.getFrequency();
+ }
+
+ ProcessingNode originalNode = originalCpu.getNode();
+ ProcessingNode processingNode = new ProcessingNode(
+ originalNode.getVendor(), originalNode.getModelName(), originalNode.getArchitecture(), 1);
+ ProcessingUnit processingUnit = new ProcessingUnit(processingNode, originalCpu.getId(), freq);
+
+ long memorySize = 0;
+ for (MemoryUnit mem : memory) {
+ memorySize += mem.getSize();
+ }
+ MemoryUnit memoryUnit = new MemoryUnit("Generic", "Generic", 3200.0, memorySize);
+
+ return new MachineModel(List.of(processingUnit), List.of(memoryUnit));
+ }
+
+ /**
+ * Return the processing units of this machine.
+ */
+ public List<ProcessingUnit> getCpus() {
+ return Collections.unmodifiableList(cpus);
+ }
+
+ /**
+ * Return the memory units of this machine.
+ */
+ public List<MemoryUnit> getMemory() {
+ return Collections.unmodifiableList(memory);
+ }
+
+ /**
+ * Return the network adapters of this machine.
+ */
+ public List<NetworkAdapter> getNetwork() {
+ return Collections.unmodifiableList(net);
+ }
+
+ /**
+ * Return the storage devices of this machine.
+ */
+ public List<StorageDevice> getStorage() {
+ return Collections.unmodifiableList(storage);
+ }
+
+ @Override
+ public boolean equals(Object o) {
+ if (this == o) return true;
+ if (o == null || getClass() != o.getClass()) return false;
+ MachineModel that = (MachineModel) o;
+ return cpus.equals(that.cpus)
+ && memory.equals(that.memory)
+ && net.equals(that.net)
+ && storage.equals(that.storage);
+ }
+
+ @Override
+ public int hashCode() {
+ return Objects.hash(cpus, memory, net, storage);
+ }
+
+ @Override
+ public String toString() {
+ return "MachineModel[cpus=" + cpus + ",memory=" + memory + ",net=" + net + ",storage=" + storage + "]";
+ }
+}
diff --git a/opendc-simulator/opendc-simulator-compute/src/main/java/org/opendc/simulator/compute/model/MemoryUnit.java b/opendc-simulator/opendc-simulator-compute/src/main/java/org/opendc/simulator/compute/model/MemoryUnit.java
new file mode 100644
index 00000000..4250f5a2
--- /dev/null
+++ b/opendc-simulator/opendc-simulator-compute/src/main/java/org/opendc/simulator/compute/model/MemoryUnit.java
@@ -0,0 +1,100 @@
+/*
+ * Copyright (c) 2022 AtLarge Research
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
+ * of this software and associated documentation files (the "Software"), to deal
+ * in the Software without restriction, including without limitation the rights
+ * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+ * copies of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included in all
+ * copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
+ * SOFTWARE.
+ */
+
+package org.opendc.simulator.compute.model;
+
+import java.util.Objects;
+
+/**
+ * A memory unit of a compute resource, either virtual or physical.
+ */
+public final class MemoryUnit {
+ private final String vendor;
+ private final String modelName;
+ private final double speed;
+ private final long size;
+
+ /**
+ * Construct a {@link ProcessingNode} instance.
+ *
+ * @param vendor The vendor of the storage device.
+ * @param modelName The model name of the device.
+ * @param speed The access speed of the memory in MHz.
+ * @param size The size of the memory unit in MBs.
+ */
+ public MemoryUnit(String vendor, String modelName, double speed, long size) {
+ this.vendor = vendor;
+ this.modelName = modelName;
+ this.speed = speed;
+ this.size = size;
+ }
+
+ /**
+ * Return the vendor of the storage device.
+ */
+ public String getVendor() {
+ return vendor;
+ }
+
+ /**
+ * Return the model name of the device.
+ */
+ public String getModelName() {
+ return modelName;
+ }
+
+ /**
+ * Return the access speed of the memory in MHz.
+ */
+ public double getSpeed() {
+ return speed;
+ }
+
+ /**
+ * Return the size of the memory unit in MBs.
+ */
+ public long getSize() {
+ return size;
+ }
+
+ @Override
+ public boolean equals(Object o) {
+ if (this == o) return true;
+ if (o == null || getClass() != o.getClass()) return false;
+ MemoryUnit that = (MemoryUnit) o;
+ return Double.compare(that.speed, speed) == 0
+ && size == that.size
+ && vendor.equals(that.vendor)
+ && modelName.equals(that.modelName);
+ }
+
+ @Override
+ public int hashCode() {
+ return Objects.hash(vendor, modelName, speed, size);
+ }
+
+ @Override
+ public String toString() {
+ return "ProcessingNode[vendor='" + vendor + "',modelName='" + modelName + "',speed=" + speed + "MHz,size="
+ + size + "MB]";
+ }
+}
diff --git a/opendc-simulator/opendc-simulator-compute/src/main/java/org/opendc/simulator/compute/model/NetworkAdapter.java b/opendc-simulator/opendc-simulator-compute/src/main/java/org/opendc/simulator/compute/model/NetworkAdapter.java
new file mode 100644
index 00000000..ff3daa40
--- /dev/null
+++ b/opendc-simulator/opendc-simulator-compute/src/main/java/org/opendc/simulator/compute/model/NetworkAdapter.java
@@ -0,0 +1,88 @@
+/*
+ * Copyright (c) 2022 AtLarge Research
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
+ * of this software and associated documentation files (the "Software"), to deal
+ * in the Software without restriction, including without limitation the rights
+ * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+ * copies of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included in all
+ * copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
+ * SOFTWARE.
+ */
+
+package org.opendc.simulator.compute.model;
+
+import java.util.Objects;
+
+/**
+ * A description of a network adapter
+ */
+public final class NetworkAdapter {
+ private final String vendor;
+ private final String modelName;
+ private final double bandwidth;
+
+ /**
+ * Construct a {@link NetworkAdapter} instance.
+ *
+ * @param vendor The vendor of the storage device.
+ * @param modelName The model name of the device.
+ * @param bandwidth The bandwidth of the network adapter in Mbps.
+ */
+ public NetworkAdapter(String vendor, String modelName, double bandwidth) {
+ this.vendor = vendor;
+ this.modelName = modelName;
+ this.bandwidth = bandwidth;
+ }
+
+ /**
+ * Return the vendor of the storage device.
+ */
+ public String getVendor() {
+ return vendor;
+ }
+
+ /**
+ * Return the model name of the device.
+ */
+ public String getModelName() {
+ return modelName;
+ }
+
+ /**
+ * Return the bandwidth of the network adapter in Mbps.
+ */
+ public double getBandwidth() {
+ return bandwidth;
+ }
+
+ @Override
+ public boolean equals(Object o) {
+ if (this == o) return true;
+ if (o == null || getClass() != o.getClass()) return false;
+ NetworkAdapter that = (NetworkAdapter) o;
+ return Double.compare(that.bandwidth, bandwidth) == 0
+ && vendor.equals(that.vendor)
+ && modelName.equals(that.modelName);
+ }
+
+ @Override
+ public int hashCode() {
+ return Objects.hash(vendor, modelName, bandwidth);
+ }
+
+ @Override
+ public String toString() {
+ return "NetworkAdapter[vendor='" + vendor + "',modelName='" + modelName + "',bandwidth=" + bandwidth + "Mbps]";
+ }
+}
diff --git a/opendc-simulator/opendc-simulator-compute/src/main/java/org/opendc/simulator/compute/model/ProcessingNode.java b/opendc-simulator/opendc-simulator-compute/src/main/java/org/opendc/simulator/compute/model/ProcessingNode.java
new file mode 100644
index 00000000..01a87b96
--- /dev/null
+++ b/opendc-simulator/opendc-simulator-compute/src/main/java/org/opendc/simulator/compute/model/ProcessingNode.java
@@ -0,0 +1,100 @@
+/*
+ * Copyright (c) 2022 AtLarge Research
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
+ * of this software and associated documentation files (the "Software"), to deal
+ * in the Software without restriction, including without limitation the rights
+ * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+ * copies of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included in all
+ * copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
+ * SOFTWARE.
+ */
+
+package org.opendc.simulator.compute.model;
+
+import java.util.Objects;
+
+/**
+ * A processing node/package/socket containing possibly several CPU cores.
+ */
+public final class ProcessingNode {
+ private final String vendor;
+ private final String modelName;
+ private final String arch;
+ private final int coreCount;
+
+ /**
+ * Construct a {@link ProcessingNode} instance.
+ *
+ * @param vendor The vendor of the storage device.
+ * @param modelName The model name of the device.
+ * @param arch The micro-architecture of the processor node.
+ * @param coreCount The number of logical CPUs in the processor node.
+ */
+ public ProcessingNode(String vendor, String modelName, String arch, int coreCount) {
+ this.vendor = vendor;
+ this.modelName = modelName;
+ this.arch = arch;
+ this.coreCount = coreCount;
+ }
+
+ /**
+ * Return the vendor of the storage device.
+ */
+ public String getVendor() {
+ return vendor;
+ }
+
+ /**
+ * Return the model name of the device.
+ */
+ public String getModelName() {
+ return modelName;
+ }
+
+ /**
+ * Return the micro-architecture of the processor node.
+ */
+ public String getArchitecture() {
+ return arch;
+ }
+
+ /**
+ * Return the number of logical CPUs in the processor node.
+ */
+ public int getCoreCount() {
+ return coreCount;
+ }
+
+ @Override
+ public boolean equals(Object o) {
+ if (this == o) return true;
+ if (o == null || getClass() != o.getClass()) return false;
+ ProcessingNode that = (ProcessingNode) o;
+ return coreCount == that.coreCount
+ && vendor.equals(that.vendor)
+ && modelName.equals(that.modelName)
+ && arch.equals(that.arch);
+ }
+
+ @Override
+ public int hashCode() {
+ return Objects.hash(vendor, modelName, arch, coreCount);
+ }
+
+ @Override
+ public String toString() {
+ return "ProcessingNode[vendor='" + vendor + "',modelName='" + modelName + "',arch=" + arch + ",coreCount="
+ + coreCount + "]";
+ }
+}
diff --git a/opendc-simulator/opendc-simulator-compute/src/main/java/org/opendc/simulator/compute/model/ProcessingUnit.java b/opendc-simulator/opendc-simulator-compute/src/main/java/org/opendc/simulator/compute/model/ProcessingUnit.java
new file mode 100644
index 00000000..51a045d1
--- /dev/null
+++ b/opendc-simulator/opendc-simulator-compute/src/main/java/org/opendc/simulator/compute/model/ProcessingUnit.java
@@ -0,0 +1,86 @@
+/*
+ * Copyright (c) 2022 AtLarge Research
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
+ * of this software and associated documentation files (the "Software"), to deal
+ * in the Software without restriction, including without limitation the rights
+ * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+ * copies of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included in all
+ * copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
+ * SOFTWARE.
+ */
+
+package org.opendc.simulator.compute.model;
+
+import java.util.Objects;
+
+/**
+ * A single logical compute unit of processor node, either virtual or physical.
+ */
+public final class ProcessingUnit {
+ private final ProcessingNode node;
+ private final int id;
+ private final double frequency;
+
+ /**
+ * Construct a {@link ProcessingUnit} instance.
+ *
+ * @param node The processing node containing the CPU core.
+ * @param id The identifier of the CPU core within the processing node.
+ * @param frequency The clock rate of the CPU in MHz.
+ */
+ public ProcessingUnit(ProcessingNode node, int id, double frequency) {
+ this.node = node;
+ this.id = id;
+ this.frequency = frequency;
+ }
+
+ /**
+ * Return the processing node containing the CPU core.
+ */
+ public ProcessingNode getNode() {
+ return node;
+ }
+
+ /**
+ * Return the identifier of the CPU core within the processing node.
+ */
+ public int getId() {
+ return id;
+ }
+
+ /**
+ * Return the clock rate of the CPU in MHz.
+ */
+ public double getFrequency() {
+ return frequency;
+ }
+
+ @Override
+ public boolean equals(Object o) {
+ if (this == o) return true;
+ if (o == null || getClass() != o.getClass()) return false;
+ ProcessingUnit that = (ProcessingUnit) o;
+ return id == that.id && Double.compare(that.frequency, frequency) == 0 && Objects.equals(node, that.node);
+ }
+
+ @Override
+ public int hashCode() {
+ return Objects.hash(node, id, frequency);
+ }
+
+ @Override
+ public String toString() {
+ return "ProcessingUnit[node=" + node + ",id=" + id + ",frequency=" + frequency + "]";
+ }
+}
diff --git a/opendc-simulator/opendc-simulator-compute/src/main/java/org/opendc/simulator/compute/model/StorageDevice.java b/opendc-simulator/opendc-simulator-compute/src/main/java/org/opendc/simulator/compute/model/StorageDevice.java
new file mode 100644
index 00000000..549ccc7e
--- /dev/null
+++ b/opendc-simulator/opendc-simulator-compute/src/main/java/org/opendc/simulator/compute/model/StorageDevice.java
@@ -0,0 +1,112 @@
+/*
+ * Copyright (c) 2022 AtLarge Research
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
+ * of this software and associated documentation files (the "Software"), to deal
+ * in the Software without restriction, including without limitation the rights
+ * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+ * copies of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included in all
+ * copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
+ * SOFTWARE.
+ */
+
+package org.opendc.simulator.compute.model;
+
+import java.util.Objects;
+
+/**
+ * Model for a physical storage device attached to a machine.
+ */
+public final class StorageDevice {
+ private final String vendor;
+ private final String modelName;
+ private final double capacity;
+ private final double readBandwidth;
+ private final double writeBandwidth;
+
+ /**
+ * Construct a {@link StorageDevice} instance.
+ *
+ * @param vendor The vendor of the storage device.
+ * @param modelName The model name of the device.
+ * @param capacity The capacity of the device.
+ * @param readBandwidth The read bandwidth of the device in MBps.
+ * @param writeBandwidth The write bandwidth of the device in MBps.
+ */
+ public StorageDevice(
+ String vendor, String modelName, double capacity, double readBandwidth, double writeBandwidth) {
+ this.vendor = vendor;
+ this.modelName = modelName;
+ this.capacity = capacity;
+ this.readBandwidth = readBandwidth;
+ this.writeBandwidth = writeBandwidth;
+ }
+
+ /**
+ * Return the vendor of the storage device.
+ */
+ public String getVendor() {
+ return vendor;
+ }
+
+ /**
+ * Return the model name of the device.
+ */
+ public String getModelName() {
+ return modelName;
+ }
+
+ /**
+ * Return the capacity of the device.
+ */
+ public double getCapacity() {
+ return capacity;
+ }
+
+ /**
+ * Return the read bandwidth of the device in MBps.
+ */
+ public double getReadBandwidth() {
+ return readBandwidth;
+ }
+
+ /**
+ * Return the write bandwidth of the device in MBps.
+ */
+ public double getWriteBandwidth() {
+ return writeBandwidth;
+ }
+
+ @Override
+ public boolean equals(Object o) {
+ if (this == o) return true;
+ if (o == null || getClass() != o.getClass()) return false;
+ StorageDevice that = (StorageDevice) o;
+ return Double.compare(that.capacity, capacity) == 0
+ && Double.compare(that.readBandwidth, readBandwidth) == 0
+ && Double.compare(that.writeBandwidth, writeBandwidth) == 0
+ && vendor.equals(that.vendor)
+ && modelName.equals(that.modelName);
+ }
+
+ @Override
+ public int hashCode() {
+ return Objects.hash(vendor, modelName, capacity, readBandwidth, writeBandwidth);
+ }
+
+ @Override
+ public String toString() {
+ return "StorageDevice[vendor='" + vendor + "',modelName='" + modelName + "',capacity=" + capacity
+ + ",readBandwidth=" + readBandwidth + ",writeBandwidth=" + writeBandwidth + "]";
+ }
+}
diff --git a/opendc-simulator/opendc-simulator-compute/src/main/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 <a href="https://dl.acm.org/doi/abs/10.1145/1273440.1250665">
+ * Fan et al., Power provisioning for a warehouse-sized computer, ACM SIGARCH'07</a>
+ */
+ public static CpuPowerModel mse(double maxPower, double idlePower, double calibrationFactor) {
+ return new MsePowerModel(maxPower, idlePower, calibrationFactor);
+ }
+
+ /**
+ * Construct an asymptotic {@link CpuPowerModel} adapted from GreenCloud.
+ *
+ * @param maxPower The maximum power draw of the server in W.
+ * @param idlePower The power draw of the server at its lowest utilization level in W.
+ * @param asymUtil A utilization level at which the server attains asymptotic,
+ * i.e., close to linear power consumption versus the offered load.
+ * For most of the CPUs,a is in [0.2, 0.5].
+ * @param dvfs A flag indicates whether DVFS is enabled.
+ */
+ public static CpuPowerModel asymptotic(double maxPower, double idlePower, double asymUtil, boolean dvfs) {
+ return new AsymptoticPowerModel(maxPower, idlePower, asymUtil, dvfs);
+ }
+
+ /**
+ * Construct a linear interpolation model {@link CpuPowerModel} that is adapted from CloudSim.
+ *
+ * <p>
+ * The power consumption is linearly interpolated over the given power levels. In case of two values, the first
+ * represents 0% utilization, while the last value represent 100% utilization.
+ *
+ * @param powerLevels An array of power consumption steps (in W) for a specific CPU utilization.
+ * @see <a href="http://www.spec.org/power_ssj2008/results/res2011q1/">Machines used in the SPEC benchmark</a>
+ */
+ public static CpuPowerModel interpolate(double... powerLevels) {
+ return new InterpolationPowerModel(powerLevels.clone());
+ }
+
+ /**
+ * Decorate an existing {@link CpuPowerModel} to ensure that zero power consumption is reported when there is no
+ * utilization.
+ *
+ * @param delegate The existing {@link CpuPowerModel} to decorate.
+ */
+ public static CpuPowerModel zeroIdle(CpuPowerModel delegate) {
+ return new ZeroIdlePowerDecorator(delegate);
+ }
+
+ private static final class ConstantPowerModel implements CpuPowerModel {
+ private final double power;
+
+ ConstantPowerModel(double power) {
+ this.power = power;
+ }
+
+ @Override
+ public double computePower(double utilization) {
+ return power;
+ }
+
+ @Override
+ public String toString() {
+ return "ConstantPowerModel[power=" + power + "]";
+ }
+ }
+
+ private abstract static class MaxIdlePowerModel implements CpuPowerModel {
+ protected final double maxPower;
+ protected final double idlePower;
+
+ MaxIdlePowerModel(double maxPower, double idlePower) {
+ this.maxPower = maxPower;
+ this.idlePower = idlePower;
+ }
+
+ @Override
+ public String toString() {
+ return getClass().getSimpleName() + "[max=" + maxPower + ",idle=" + idlePower + "]";
+ }
+ }
+
+ private static final class SqrtPowerModel extends MaxIdlePowerModel {
+ private final double factor;
+
+ SqrtPowerModel(double maxPower, double idlePower) {
+ super(maxPower, idlePower);
+ this.factor = (maxPower - idlePower) / Math.sqrt(100);
+ }
+
+ @Override
+ public double computePower(double utilization) {
+ return idlePower + factor * Math.sqrt(utilization * 100);
+ }
+ }
+
+ private static final class LinearPowerModel extends MaxIdlePowerModel {
+ private final double factor;
+
+ LinearPowerModel(double maxPower, double idlePower) {
+ super(maxPower, idlePower);
+ this.factor = (maxPower - idlePower) / 100;
+ }
+
+ @Override
+ public double computePower(double utilization) {
+ return idlePower + factor * utilization * 100;
+ }
+ }
+
+ private static final class SquarePowerModel extends MaxIdlePowerModel {
+ private final double factor;
+
+ SquarePowerModel(double maxPower, double idlePower) {
+ super(maxPower, idlePower);
+ this.factor = (maxPower - idlePower) / Math.pow(100, 2);
+ }
+
+ @Override
+ public double computePower(double utilization) {
+ return idlePower + factor * Math.pow(utilization * 100, 2);
+ }
+ }
+
+ private static final class CubicPowerModel extends MaxIdlePowerModel {
+ private final double factor;
+
+ CubicPowerModel(double maxPower, double idlePower) {
+ super(maxPower, idlePower);
+ this.factor = (maxPower - idlePower) / Math.pow(100, 3);
+ }
+
+ @Override
+ public double computePower(double utilization) {
+ return idlePower + factor * Math.pow(utilization * 100, 3);
+ }
+ }
+
+ private static final class MsePowerModel extends MaxIdlePowerModel {
+ private final double calibrationFactor;
+ private final double factor;
+
+ MsePowerModel(double maxPower, double idlePower, double calibrationFactor) {
+ super(maxPower, idlePower);
+ this.calibrationFactor = calibrationFactor;
+ this.factor = (maxPower - idlePower) / 100;
+ }
+
+ @Override
+ public double computePower(double utilization) {
+ return idlePower + factor * (2 * utilization - Math.pow(utilization, calibrationFactor)) * 100;
+ }
+
+ @Override
+ public String toString() {
+ return "MsePowerModel[max=" + maxPower + ",idle=" + idlePower + ",calibrationFactor=" + calibrationFactor
+ + "]";
+ }
+ }
+
+ private static final class AsymptoticPowerModel extends MaxIdlePowerModel {
+ private final double asymUtil;
+ private final boolean dvfs;
+ private final double factor;
+
+ AsymptoticPowerModel(double maxPower, double idlePower, double asymUtil, boolean dvfs) {
+ super(maxPower, idlePower);
+ this.asymUtil = asymUtil;
+ this.dvfs = dvfs;
+ this.factor = (maxPower - idlePower) / 100;
+ }
+
+ @Override
+ public double computePower(double utilization) {
+ if (dvfs) {
+ return idlePower
+ + (factor * 100)
+ / 2
+ * (1
+ + Math.pow(utilization, 3)
+ - Math.pow(Math.E, -Math.pow(utilization, 3) / asymUtil));
+ } else {
+ return idlePower + (factor * 100) / 2 * (1 + utilization - Math.pow(Math.E, -utilization / asymUtil));
+ }
+ }
+
+ @Override
+ public String toString() {
+ return "AsymptoticPowerModel[max=" + maxPower + ",idle=" + idlePower + ",asymUtil=" + asymUtil + ",dvfs="
+ + dvfs + "]";
+ }
+ }
+
+ private static final class InterpolationPowerModel implements CpuPowerModel {
+ private final double[] powerLevels;
+
+ InterpolationPowerModel(double[] powerLevels) {
+ this.powerLevels = powerLevels;
+ }
+
+ @Override
+ public double computePower(double utilization) {
+ final double[] powerLevels = this.powerLevels;
+ double clampedUtilization = Math.min(1.0, Math.max(0.0, utilization));
+
+ if (utilization % 0.1 == 0.0) {
+ return powerLevels[(int) (clampedUtilization * 10)];
+ }
+
+ int utilizationFlr = (int) Math.floor(clampedUtilization * 10);
+ int utilizationCil = (int) Math.ceil(clampedUtilization * 10);
+ double powerFlr = powerLevels[utilizationFlr];
+ double powerCil = powerLevels[utilizationCil];
+ double delta = (powerCil - powerFlr) / 10;
+
+ return powerFlr + delta * (clampedUtilization - utilizationFlr / 10.0) * 100;
+ }
+
+ @Override
+ public String toString() {
+ return "InterpolationPowerModel[levels=" + Arrays.toString(powerLevels) + "]";
+ }
+ }
+
+ private static final class ZeroIdlePowerDecorator implements CpuPowerModel {
+ private final CpuPowerModel delegate;
+
+ ZeroIdlePowerDecorator(CpuPowerModel delegate) {
+ this.delegate = delegate;
+ }
+
+ @Override
+ public double computePower(double utilization) {
+ if (utilization == 0.0) {
+ return 0.0;
+ }
+
+ return delegate.computePower(utilization);
+ }
+
+ @Override
+ public String toString() {
+ return "ZeroIdlePowerDecorator[delegate=" + delegate + "]";
+ }
+ }
+}
diff --git a/opendc-simulator/opendc-simulator-compute/src/main/java/org/opendc/simulator/compute/workload/SimFlopsWorkload.java b/opendc-simulator/opendc-simulator-compute/src/main/java/org/opendc/simulator/compute/workload/SimFlopsWorkload.java
new file mode 100644
index 00000000..255fd1b2
--- /dev/null
+++ b/opendc-simulator/opendc-simulator-compute/src/main/java/org/opendc/simulator/compute/workload/SimFlopsWorkload.java
@@ -0,0 +1,141 @@
+/*
+ * Copyright (c) 2022 AtLarge Research
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
+ * of this software and associated documentation files (the "Software"), to deal
+ * in the Software without restriction, including without limitation the rights
+ * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+ * copies of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included in all
+ * copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
+ * SOFTWARE.
+ */
+
+package org.opendc.simulator.compute.workload;
+
+import java.util.List;
+import org.opendc.simulator.compute.SimMachineContext;
+import org.opendc.simulator.compute.SimProcessingUnit;
+import org.opendc.simulator.flow2.FlowGraph;
+import org.opendc.simulator.flow2.FlowStage;
+import org.opendc.simulator.flow2.FlowStageLogic;
+import org.opendc.simulator.flow2.OutPort;
+
+/**
+ * A {@link SimWorkload} that models applications as a static number of floating point operations executed on
+ * multiple cores of a compute resource.
+ */
+public class SimFlopsWorkload implements SimWorkload, FlowStageLogic {
+ private final long flops;
+ private final double utilization;
+
+ private SimMachineContext ctx;
+ private FlowStage stage;
+ private OutPort[] outputs;
+
+ private float remainingAmount;
+ private long lastUpdate;
+
+ /**
+ * Construct a new {@link SimFlopsWorkload}.
+ *
+ * @param flops The number of floating point operations to perform for this task in MFLOPs.
+ * @param utilization A model of the CPU utilization of the application.
+ */
+ public SimFlopsWorkload(long flops, double utilization) {
+ if (flops < 0) {
+ throw new IllegalArgumentException("Number of FLOPs must be positive");
+ } else if (utilization <= 0.0 || utilization > 1.0) {
+ throw new IllegalArgumentException("Utilization must be in (0, 1]");
+ }
+
+ this.flops = flops;
+ this.utilization = utilization;
+ }
+
+ @Override
+ public void onStart(SimMachineContext ctx) {
+ this.ctx = ctx;
+
+ final FlowGraph graph = ctx.getGraph();
+ final FlowStage stage = graph.newStage(this);
+ this.stage = stage;
+
+ final List<? extends SimProcessingUnit> cpus = ctx.getCpus();
+ final OutPort[] outputs = new OutPort[cpus.size()];
+ this.outputs = outputs;
+
+ for (int i = 0; i < cpus.size(); i++) {
+ final SimProcessingUnit cpu = cpus.get(i);
+ final OutPort output = stage.getOutlet("cpu" + i);
+
+ graph.connect(output, cpu.getInput());
+ outputs[i] = output;
+ }
+
+ this.remainingAmount = flops;
+ this.lastUpdate = graph.getEngine().getClock().millis();
+ }
+
+ @Override
+ public void onStop(SimMachineContext ctx) {
+ this.ctx = null;
+
+ final FlowStage stage = this.stage;
+ if (stage != null) {
+ this.stage = null;
+ stage.close();
+ }
+ }
+
+ @Override
+ public String toString() {
+ return "SimFlopsWorkload[FLOPs=" + flops + ",utilization=" + utilization + "]";
+ }
+
+ @Override
+ public long onUpdate(FlowStage ctx, long now) {
+ long lastUpdate = this.lastUpdate;
+ this.lastUpdate = now;
+
+ long delta = Math.max(0, now - lastUpdate);
+
+ float consumed = 0.f;
+ float limit = 0.f;
+
+ for (final OutPort output : outputs) {
+ consumed += output.getRate() * delta;
+
+ float outputLimit = (float) (output.getCapacity() * utilization);
+ limit += outputLimit;
+
+ output.push(outputLimit);
+ }
+ consumed = (float) (consumed * 0.001);
+
+ float remainingAmount = this.remainingAmount - consumed;
+ this.remainingAmount = remainingAmount;
+
+ long duration = (long) Math.ceil(remainingAmount / limit * 1000);
+
+ if (duration <= 0) {
+ final SimMachineContext machineContext = this.ctx;
+ if (machineContext != null) {
+ machineContext.shutdown();
+ }
+ ctx.close();
+ return Long.MAX_VALUE;
+ }
+
+ return now + duration;
+ }
+}
diff --git a/opendc-simulator/opendc-simulator-compute/src/main/java/org/opendc/simulator/compute/workload/SimRuntimeWorkload.java b/opendc-simulator/opendc-simulator-compute/src/main/java/org/opendc/simulator/compute/workload/SimRuntimeWorkload.java
new file mode 100644
index 00000000..c3380b31
--- /dev/null
+++ b/opendc-simulator/opendc-simulator-compute/src/main/java/org/opendc/simulator/compute/workload/SimRuntimeWorkload.java
@@ -0,0 +1,131 @@
+/*
+ * Copyright (c) 2022 AtLarge Research
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
+ * of this software and associated documentation files (the "Software"), to deal
+ * in the Software without restriction, including without limitation the rights
+ * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+ * copies of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included in all
+ * copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
+ * SOFTWARE.
+ */
+
+package org.opendc.simulator.compute.workload;
+
+import java.util.List;
+import org.opendc.simulator.compute.SimMachineContext;
+import org.opendc.simulator.compute.SimProcessingUnit;
+import org.opendc.simulator.flow2.FlowGraph;
+import org.opendc.simulator.flow2.FlowStage;
+import org.opendc.simulator.flow2.FlowStageLogic;
+import org.opendc.simulator.flow2.OutPort;
+
+/**
+ * A [SimWorkload] that models application execution as a single duration.
+ */
+public class SimRuntimeWorkload implements SimWorkload, FlowStageLogic {
+ private final long duration;
+ private final double utilization;
+
+ private SimMachineContext ctx;
+ private FlowStage stage;
+ private OutPort[] outputs;
+
+ private long remainingDuration;
+ private long lastUpdate;
+
+ /**
+ * Construct a new {@link SimRuntimeWorkload}.
+ *
+ * @param duration The duration of the workload in milliseconds.
+ * @param utilization A model of the CPU utilization of the application.
+ */
+ public SimRuntimeWorkload(long duration, double utilization) {
+ if (duration < 0) {
+ throw new IllegalArgumentException("Duration must be positive");
+ } else if (utilization <= 0.0 || utilization > 1.0) {
+ throw new IllegalArgumentException("Utilization must be in (0, 1]");
+ }
+
+ this.duration = duration;
+ this.utilization = utilization;
+ }
+
+ @Override
+ public void onStart(SimMachineContext ctx) {
+ this.ctx = ctx;
+
+ final FlowGraph graph = ctx.getGraph();
+ final FlowStage stage = graph.newStage(this);
+ this.stage = stage;
+
+ final List<? extends SimProcessingUnit> cpus = ctx.getCpus();
+ final OutPort[] outputs = new OutPort[cpus.size()];
+ this.outputs = outputs;
+
+ for (int i = 0; i < cpus.size(); i++) {
+ final SimProcessingUnit cpu = cpus.get(i);
+ final OutPort output = stage.getOutlet("cpu" + i);
+
+ graph.connect(output, cpu.getInput());
+ outputs[i] = output;
+ }
+
+ this.remainingDuration = duration;
+ this.lastUpdate = graph.getEngine().getClock().millis();
+ }
+
+ @Override
+ public void onStop(SimMachineContext ctx) {
+ this.ctx = null;
+
+ final FlowStage stage = this.stage;
+ if (stage != null) {
+ this.stage = null;
+ this.outputs = null;
+ stage.close();
+ }
+ }
+
+ @Override
+ public long onUpdate(FlowStage ctx, long now) {
+ long lastUpdate = this.lastUpdate;
+ this.lastUpdate = now;
+
+ long delta = now - lastUpdate;
+ long duration = this.remainingDuration - delta;
+
+ if (duration <= 0) {
+ final SimMachineContext machineContext = this.ctx;
+ if (machineContext != null) {
+ machineContext.shutdown();
+ }
+ ctx.close();
+ return Long.MAX_VALUE;
+ }
+
+ this.remainingDuration = duration;
+
+ for (final OutPort output : outputs) {
+ float limit = (float) (output.getCapacity() * utilization);
+ output.push(limit);
+ }
+
+ return now + duration;
+ }
+
+ @Override
+ public String toString() {
+ return "SimDurationWorkload[duration=" + duration + "ms,utilization=" + utilization + "]";
+ }
+}
diff --git a/opendc-simulator/opendc-simulator-compute/src/main/java/org/opendc/simulator/compute/workload/SimTrace.java b/opendc-simulator/opendc-simulator-compute/src/main/java/org/opendc/simulator/compute/workload/SimTrace.java
new file mode 100644
index 00000000..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<SimTraceFragment> fragments) {
+ final Builder builder = builder(fragments.size());
+
+ for (SimTraceFragment fragment : fragments) {
+ builder.add(fragment.timestamp + fragment.duration, fragment.usage, fragment.cores);
+ }
+
+ return builder.build();
+ }
+
+ /**
+ * Builder class for a {@link SimTrace}.
+ */
+ public static final class Builder {
+ private double[] usageCol;
+ private long[] deadlineCol;
+ private int[] coresCol;
+
+ private int size;
+ private boolean isBuilt;
+
+ /**
+ * Construct a new {@link Builder} instance.
+ */
+ private Builder(int initialCapacity) {
+ this.usageCol = new double[initialCapacity];
+ this.deadlineCol = new long[initialCapacity];
+ this.coresCol = new int[initialCapacity];
+ }
+
+ /**
+ * Add a fragment to the trace.
+ *
+ * @param deadline The timestamp at which the fragment ends (in epoch millis).
+ * @param usage The CPU usage at this fragment.
+ * @param cores The number of cores used during this fragment.
+ */
+ public void add(long deadline, double usage, int cores) {
+ if (isBuilt) {
+ recreate();
+ }
+
+ int size = this.size;
+ double[] usageCol = this.usageCol;
+
+ if (size == usageCol.length) {
+ grow();
+ usageCol = this.usageCol;
+ }
+
+ deadlineCol[size] = deadline;
+ usageCol[size] = usage;
+ coresCol[size] = cores;
+
+ this.size++;
+ }
+
+ /**
+ * Build the {@link SimTrace} instance.
+ */
+ public SimTrace build() {
+ isBuilt = true;
+ return new SimTrace(usageCol, deadlineCol, coresCol, size);
+ }
+
+ /**
+ * Helper method to grow the capacity of the trace.
+ */
+ private void grow() {
+ int arraySize = usageCol.length;
+ int newSize = arraySize + (arraySize >> 1);
+
+ usageCol = Arrays.copyOf(usageCol, newSize);
+ deadlineCol = Arrays.copyOf(deadlineCol, newSize);
+ coresCol = Arrays.copyOf(coresCol, newSize);
+ }
+
+ /**
+ * Clone the columns of the trace.
+ *
+ * <p>
+ * This is necessary when a {@link SimTrace} has been built already, but the user is again adding entries to
+ * the builder.
+ */
+ private void recreate() {
+ isBuilt = false;
+ usageCol = usageCol.clone();
+ deadlineCol = deadlineCol.clone();
+ coresCol = coresCol.clone();
+ }
+ }
+
+ /**
+ * Implementation of {@link SimWorkload} that executes a trace.
+ */
+ private static class Workload implements SimWorkload, 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<? extends SimProcessingUnit> cpus = ctx.getCpus();
+
+ stage = graph.newStage(this);
+ coreCount = cpus.size();
+
+ final OutPort[] outputs = new OutPort[cpus.size()];
+ this.outputs = outputs;
+
+ for (int i = 0; i < cpus.size(); i++) {
+ final SimProcessingUnit cpu = cpus.get(i);
+ final OutPort output = stage.getOutlet("cpu" + i);
+
+ graph.connect(output, cpu.getInput());
+ outputs[i] = output;
+ }
+ }
+
+ @Override
+ public 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.
+ *
+ * <p>
+ * 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<Runnable> 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<Runnable> waiting = SimWorkloadLifecycle.this.waiting;
+ if (waiting.remove(this) && waiting.isEmpty()) {
+ ctx.shutdown();
+ }
+ }
+ };
+ waiting.add(completer);
+ return completer;
+ }
+}