diff --git a/opendc-compute/opendc-compute-service/src/main/java/org/opendc/compute/service/driver/telemetry/HostSystemStats.java b/opendc-compute/opendc-compute-service/src/main/java/org/opendc/compute/service/driver/telemetry/HostSystemStats.java index d9dba274c..68b22d754 100644 --- a/opendc-compute/opendc-compute-service/src/main/java/org/opendc/compute/service/driver/telemetry/HostSystemStats.java +++ b/opendc-compute/opendc-compute-service/src/main/java/org/opendc/compute/service/driver/telemetry/HostSystemStats.java @@ -44,6 +44,7 @@ public record HostSystemStats( Instant bootTime, double powerDraw, double energyUsage, + double cpuTemperature, int guestsTerminated, int guestsRunning, int guestsError, diff --git a/opendc-compute/opendc-compute-simulator/src/main/kotlin/org/opendc/compute/simulator/SimHost.kt b/opendc-compute/opendc-compute-simulator/src/main/kotlin/org/opendc/compute/simulator/SimHost.kt index 624a612f5..3f9f429bc 100644 --- a/opendc-compute/opendc-compute-simulator/src/main/kotlin/org/opendc/compute/simulator/SimHost.kt +++ b/opendc-compute/opendc-compute-simulator/src/main/kotlin/org/opendc/compute/simulator/SimHost.kt @@ -237,6 +237,7 @@ public class SimHost( localBootTime, machine.psu.powerDraw, machine.psu.energyUsage, + machine.psu.cpuTemperature, terminated, running, error, diff --git a/opendc-compute/opendc-compute-telemetry/src/main/kotlin/org/opendc/compute/telemetry/ComputeMetricReader.kt b/opendc-compute/opendc-compute-telemetry/src/main/kotlin/org/opendc/compute/telemetry/ComputeMetricReader.kt index 0b11b57da..698c0cb02 100644 --- a/opendc-compute/opendc-compute-telemetry/src/main/kotlin/org/opendc/compute/telemetry/ComputeMetricReader.kt +++ b/opendc-compute/opendc-compute-telemetry/src/main/kotlin/org/opendc/compute/telemetry/ComputeMetricReader.kt @@ -247,6 +247,7 @@ public class ComputeMetricReader( _cpuLostTime = table.cpuLostTime _powerDraw = table.powerDraw _energyUsage = table.energyUsage + _cpuTemperature = table.cpuTemperature _carbonIntensity = table.carbonIntensity _carbonEmission = table.carbonEmission _uptime = table.uptime @@ -328,6 +329,10 @@ public class ComputeMetricReader( private var _energyUsage = 0.0 private var previousPowerTotal = 0.0 + override val cpuTemperature: Double + get() = _cpuTemperature + private var _cpuTemperature = 0.0 + override val carbonIntensity: Double get() = _carbonIntensity private var _carbonIntensity = 0.0 @@ -378,6 +383,7 @@ public class ComputeMetricReader( _cpuLostTime = hostCpuStats.lostTime _powerDraw = hostSysStats.powerDraw _energyUsage = hostSysStats.energyUsage + _cpuTemperature = hostSysStats.cpuTemperature _carbonIntensity = carbonTrace.getCarbonIntensity(timestampAbsolute) _carbonEmission = carbonIntensity * (energyUsage / 3600000.0) // convert energy usage from J to kWh @@ -412,6 +418,7 @@ public class ComputeMetricReader( _powerDraw = 0.0 _energyUsage = 0.0 + _cpuTemperature = 0.0 _carbonIntensity = 0.0 _carbonEmission = 0.0 } diff --git a/opendc-compute/opendc-compute-telemetry/src/main/kotlin/org/opendc/compute/telemetry/export/parquet/ParquetHostDataWriter.kt b/opendc-compute/opendc-compute-telemetry/src/main/kotlin/org/opendc/compute/telemetry/export/parquet/ParquetHostDataWriter.kt index 020e67f24..ff8a63feb 100644 --- a/opendc-compute/opendc-compute-telemetry/src/main/kotlin/org/opendc/compute/telemetry/export/parquet/ParquetHostDataWriter.kt +++ b/opendc-compute/opendc-compute-telemetry/src/main/kotlin/org/opendc/compute/telemetry/export/parquet/ParquetHostDataWriter.kt @@ -152,34 +152,38 @@ public class ParquetHostDataWriter(path: File, bufferSize: Int) : consumer.addDouble(data.energyUsage) consumer.endField("energy_usage", 19) - consumer.startField("carbon_intensity", 20) + consumer.startField("cpuTemperature", 20) + consumer.addDouble(data.cpuTemperature) + consumer.endField("cpuTemperature", 20) + + consumer.startField("carbon_intensity", 21) consumer.addDouble(data.carbonIntensity) - consumer.endField("carbon_intensity", 20) + consumer.endField("carbon_intensity", 21) - consumer.startField("carbon_emission", 21) + consumer.startField("carbon_emission", 22) consumer.addDouble(data.carbonEmission) - consumer.endField("carbon_emission", 21) + consumer.endField("carbon_emission", 22) - consumer.startField("uptime", 22) + consumer.startField("uptime", 23) consumer.addLong(data.uptime) - consumer.endField("uptime", 22) + consumer.endField("uptime", 23) - consumer.startField("downtime", 23) + consumer.startField("downtime", 24) consumer.addLong(data.downtime) - consumer.endField("downtime", 23) + consumer.endField("downtime", 24) val bootTime = data.bootTime if (bootTime != null) { - consumer.startField("boot_time", 24) + consumer.startField("boot_time", 25) consumer.addLong(bootTime.toEpochMilli()) - consumer.endField("boot_time", 24) + consumer.endField("boot_time", 25) } val bootTimeAbsolute = data.bootTimeAbsolute if (bootTimeAbsolute != null) { - consumer.startField("boot_time_absolute", 25) + consumer.startField("boot_time_absolute", 26) consumer.addLong(bootTimeAbsolute.toEpochMilli()) - consumer.endField("boot_time_absolute", 25) + consumer.endField("boot_time_absolute", 26) } consumer.endMessage() @@ -256,6 +260,9 @@ public class ParquetHostDataWriter(path: File, bufferSize: Int) : Types .required(PrimitiveType.PrimitiveTypeName.DOUBLE) .named("energy_usage"), + Types + .required(PrimitiveType.PrimitiveTypeName.DOUBLE) + .named("cpuTemperature"), Types .required(PrimitiveType.PrimitiveTypeName.DOUBLE) .named("carbon_intensity"), diff --git a/opendc-compute/opendc-compute-telemetry/src/main/kotlin/org/opendc/compute/telemetry/table/HostTableReader.kt b/opendc-compute/opendc-compute-telemetry/src/main/kotlin/org/opendc/compute/telemetry/table/HostTableReader.kt index d41c6dc08..b1971219d 100644 --- a/opendc-compute/opendc-compute-telemetry/src/main/kotlin/org/opendc/compute/telemetry/table/HostTableReader.kt +++ b/opendc-compute/opendc-compute-telemetry/src/main/kotlin/org/opendc/compute/telemetry/table/HostTableReader.kt @@ -117,6 +117,11 @@ public interface HostTableReader { */ public val energyUsage: Double + /** + * The current temperature of the host in degrees Celsius. + */ + public val cpuTemperature: Double + /** * The current carbon intensity of the host in gCO2 / kW. */ diff --git a/opendc-compute/opendc-compute-topology/src/main/kotlin/org/opendc/compute/topology/TopologyFactories.kt b/opendc-compute/opendc-compute-topology/src/main/kotlin/org/opendc/compute/topology/TopologyFactories.kt index 656085e84..ee1be5aa7 100644 --- a/opendc-compute/opendc-compute-topology/src/main/kotlin/org/opendc/compute/topology/TopologyFactories.kt +++ b/opendc-compute/opendc-compute-topology/src/main/kotlin/org/opendc/compute/topology/TopologyFactories.kt @@ -34,6 +34,7 @@ import org.opendc.simulator.compute.model.MemoryUnit import org.opendc.simulator.compute.model.ProcessingNode import org.opendc.simulator.compute.model.ProcessingUnit import org.opendc.simulator.compute.power.getPowerModel +import org.opendc.simulator.compute.thermal.getThermalModel import java.io.File import java.io.InputStream import java.util.SplittableRandom @@ -130,6 +131,16 @@ private fun HostJSONSpec.toHostSpecs( ) val powerModel = getPowerModel(powerModel.modelType, powerModel.power, powerModel.maxPower, powerModel.idlePower) + val thermalModel = + getThermalModel( + thermalModel.modelType, + thermalModel.rHS, + thermalModel.rCase, + thermalModel.minLeakageCurrent, + thermalModel.maxLeakageCurrent, + thermalModel.supplyVoltage, + thermalModel.ambientTemperature, + ) var hostName: String if (name == null) { @@ -144,7 +155,7 @@ private fun HostJSONSpec.toHostSpecs( hostName, mapOf("cluster" to clusterId), machineModel, - SimPsuFactories.simple(powerModel), + SimPsuFactories.simple(powerModel, thermalModel), ) hostId++ diff --git a/opendc-compute/opendc-compute-topology/src/main/kotlin/org/opendc/compute/topology/specs/TopologySpecs.kt b/opendc-compute/opendc-compute-topology/src/main/kotlin/org/opendc/compute/topology/specs/TopologySpecs.kt index 110d6fb15..ec28c816d 100644 --- a/opendc-compute/opendc-compute-topology/src/main/kotlin/org/opendc/compute/topology/specs/TopologySpecs.kt +++ b/opendc-compute/opendc-compute-topology/src/main/kotlin/org/opendc/compute/topology/specs/TopologySpecs.kt @@ -58,6 +58,7 @@ public data class ClusterSpec( * @param memory The amount of RAM memory available in Byte * @param powerModel The power model used to determine the power draw of a host * @param count The power model used to determine the power draw of a host + * @param thermalModel The thermal model used to determine the temperature of a host - defaults to Intel Xeon Platinum 8160 */ @Serializable public data class HostJSONSpec( @@ -65,6 +66,16 @@ public data class HostJSONSpec( val cpu: CPUSpec, val memory: MemorySpec, val powerModel: PowerModelSpec = PowerModelSpec("linear", 350.0, 400.0, 200.0), + val thermalModel: ThermalModelSpec = + ThermalModelSpec( + "rcmodel", + 0.298, + 0.00061, + 0.00035, + 0.0041, + 1.8, + 22.0, + ), val count: Int = 1, ) @@ -116,3 +127,14 @@ public data class PowerModelSpec( require(maxPower >= idlePower) { "The max power of a power model can not be less than the idle power" } } } + +@Serializable +public data class ThermalModelSpec( + val modelType: String, + val rHS: Double, + val rCase: Double, + val minLeakageCurrent: Double, + val maxLeakageCurrent: Double, + val supplyVoltage: Double, + val ambientTemperature: Double, +) 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 index 11356eb28..4be6241d3 100644 --- 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 @@ -61,7 +61,6 @@ public final class SimBareMetalMachine extends SimAbstractMachine { private final Memory memory; private final List net; private final List disk; - /** * Construct a {@link SimBareMetalMachine} instance. * 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 index 68dae4bfe..2ce54e792 100644 --- 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 @@ -45,6 +45,16 @@ public abstract class SimPsu extends SimPowerInlet { */ public abstract double getPowerDraw(); + /** + * Return the temperature of the Host in degrees Celsius based on a power equation. + */ + public abstract double getCpuTemperature(); + + /** + * Set the temperature of the Host in degrees Celsius based on a power equation. + */ + public abstract void setTemperature(); + /** * Return the cumulated energy usage of the machine (in J) measured at the inlet of the powers supply. */ 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 index 5b1184292..82679ec31 100644 --- 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 @@ -26,6 +26,7 @@ import org.jetbrains.annotations.NotNull; import org.opendc.simulator.compute.model.ProcessingUnit; import org.opendc.simulator.compute.power.CpuPowerModel; +import org.opendc.simulator.compute.thermal.ThermalModel; import org.opendc.simulator.flow2.FlowGraph; import org.opendc.simulator.flow2.FlowStage; import org.opendc.simulator.flow2.FlowStageLogic; @@ -57,8 +58,8 @@ public static SimPsuFactory noop() { * * @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); + public static SimPsuFactory simple(CpuPowerModel model, ThermalModel thermalModel) { + return (machine, graph) -> new SimplePsu(graph, model, thermalModel); } /** @@ -91,6 +92,14 @@ public double getEnergyUsage() { return 0; } + @Override + public double getCpuTemperature() { + return 0; + } + + @Override + public void setTemperature() {} + @Override InPort getCpuPower(int id, ProcessingUnit model) { final InPort port = stage.getInlet("cpu" + id); @@ -117,6 +126,7 @@ private static final class SimplePsu extends SimPsu implements FlowStageLogic { private final FlowStage stage; private final OutPort out; private final CpuPowerModel model; + private final ThermalModel thermalModel; private final InstantSource clock; private double targetFreq; @@ -125,6 +135,10 @@ private static final class SimplePsu extends SimPsu implements FlowStageLogic { private double powerDraw; private double energyUsage; + private double voltage = 1.8; // FIXME implement voltage when PSU is fully implemented + private double current; // FIXME implement current when PSU is fully implemented + private double staticPower; + private double machineTemp; private final InHandler handler = new InHandler() { @Override @@ -138,10 +152,11 @@ public void onUpstreamFinish(InPort port, Throwable cause) { } }; - SimplePsu(FlowGraph graph, CpuPowerModel model) { + SimplePsu(FlowGraph graph, CpuPowerModel model, ThermalModel thermalModel) { this.stage = graph.newStage(this); this.model = model; this.clock = graph.getEngine().getClock(); + this.thermalModel = thermalModel; this.out = stage.getOutlet("out"); this.out.setMask(true); @@ -158,6 +173,19 @@ public double getPowerDraw() { return powerDraw; } + @Override + public void setTemperature() { + double minPower = model.computePower(0.0); + double maxPower = model.computePower(1.0); + + machineTemp = thermalModel.setTemperature(powerDraw, minPower, maxPower); + } + + @Override + public double getCpuTemperature() { + return machineTemp; + } + @Override public double getEnergyUsage() { updateEnergyUsage(clock.millis()); @@ -188,6 +216,8 @@ public long onUpdate(FlowStage ctx, long now) { out.push((float) usage); powerDraw = usage; + setTemperature(); + return Long.MAX_VALUE; } diff --git a/opendc-simulator/opendc-simulator-compute/src/main/java/org/opendc/simulator/compute/thermal/ThermalModel.java b/opendc-simulator/opendc-simulator-compute/src/main/java/org/opendc/simulator/compute/thermal/ThermalModel.java new file mode 100644 index 000000000..b45242450 --- /dev/null +++ b/opendc-simulator/opendc-simulator-compute/src/main/java/org/opendc/simulator/compute/thermal/ThermalModel.java @@ -0,0 +1,28 @@ +/* + * Copyright (c) 2024 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.thermal; + +public interface ThermalModel { + + double setTemperature(double dynamicPower, double minPower, double maxPower); +} diff --git a/opendc-simulator/opendc-simulator-compute/src/main/java/org/opendc/simulator/compute/thermal/ThermalModelFactories.kt b/opendc-simulator/opendc-simulator-compute/src/main/java/org/opendc/simulator/compute/thermal/ThermalModelFactories.kt new file mode 100644 index 000000000..bf46d0f08 --- /dev/null +++ b/opendc-simulator/opendc-simulator-compute/src/main/java/org/opendc/simulator/compute/thermal/ThermalModelFactories.kt @@ -0,0 +1,79 @@ +/* + * Copyright (c) 2024 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.thermal + +// FIXME: This currently only works for RC models, we need to generalize this to support more models. + +/** + * A factory for creating thermal models. + * @param modelType The type of the thermal model to create. + * @param rHS The thermal resistance between the heat source and the heat sink. + * @param rCase The thermal resistance between the heat sink and the ambient. + * @param minLeakageCurrent The minimum leakage current of the heat source. + * @param maxLeakageCurrent The maximum leakage current of the heat source. + * @param supplyVoltage The supply voltage of the heat source. + * @param ambientTemperature The ambient temperature of the heat source. + * @return The thermal model. + */ +public fun getThermalModel( + modelType: String, + rHS: Double, + rCase: Double, + minLeakageCurrent: Double, + maxLeakageCurrent: Double, + supplyVoltage: Double, + ambientTemperature: Double, +): ThermalModel { + return when (modelType) { + "rcmodel" -> + ThermalModels.rcmodel( + rHS, + rCase, + minLeakageCurrent, + maxLeakageCurrent, + supplyVoltage, + ambientTemperature, + ) + + else -> throw IllegalArgumentException("Unknown thermal modelType $modelType") + } +} + +/** + * The default factory for creating RC thermal model using the values from an Intel Xeon Platinum 8160. + */ +public fun getThermalModel(modelType: String): ThermalModel { + return when (modelType) { + "rcmodel" -> + ThermalModels.rcmodel( + 0.298, + 0.00061, + 0.00035, + 0.0041, + 1.8, + 22.0, + ) + + else -> throw IllegalArgumentException("Unknown thermal modelType $modelType") + } +} diff --git a/opendc-simulator/opendc-simulator-compute/src/main/java/org/opendc/simulator/compute/thermal/ThermalModels.java b/opendc-simulator/opendc-simulator-compute/src/main/java/org/opendc/simulator/compute/thermal/ThermalModels.java new file mode 100644 index 000000000..97704dc08 --- /dev/null +++ b/opendc-simulator/opendc-simulator-compute/src/main/java/org/opendc/simulator/compute/thermal/ThermalModels.java @@ -0,0 +1,100 @@ +/* + * Copyright (c) 2024 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.thermal; + +public class ThermalModels { + + public static ThermalModel rcmodel( + double rHS, + double rCase, + double minLeakageCurrent, + double maxLeakageCurrent, + double supplyVoltage, + double ambientTemperature) { + return new RCModel(rHS, rCase, minLeakageCurrent, maxLeakageCurrent, supplyVoltage, ambientTemperature); + } + + public static ThermalModel manufacturerModel(double slope, double intercept) { + return new ManufacturerModel(slope, intercept); + } + + private static class RCModel implements ThermalModel { + protected final double rHS; + protected final double rCase; + protected final double minLeakageCurrent; + protected final double maxLeakageCurrent; + protected final double ambientTemperature; + protected final double supplyVoltage; + + private double currentStaticPower; + private double totalPowerDissipated; + + private RCModel( + double rHS, + double rCase, + double minLeakageCurrent, + double maxLeakageCurrent, + double supplyVoltage, + double ambientTemperature) { + this.rHS = rHS; + this.rCase = rCase; + this.minLeakageCurrent = minLeakageCurrent; + this.maxLeakageCurrent = maxLeakageCurrent; + this.supplyVoltage = supplyVoltage; + this.ambientTemperature = ambientTemperature; + } + + private void setStaticPower(double dynamicPower, double minPower, double maxPower) { + currentStaticPower = + ((maxLeakageCurrent - minLeakageCurrent) / (maxPower - minPower) * (dynamicPower - minPower)) + + minLeakageCurrent; + } + + private void setThermalPower(double dynamicPower, double minPower) { + totalPowerDissipated = dynamicPower + minPower + currentStaticPower; + } + + @Override + public double setTemperature(double dynamicPower, double minPower, double maxPower) { + setStaticPower(dynamicPower, minPower, maxPower); + setThermalPower(dynamicPower, minPower); + + return ambientTemperature + (totalPowerDissipated * (rHS + rCase)); + } + } + + private static class ManufacturerModel implements ThermalModel { + protected final double slope; + protected final double intercept; + + private ManufacturerModel(double slope, double intercept) { + this.slope = slope; + this.intercept = intercept; + } + + @Override + public double setTemperature(double dynamicPower, double minPower, double maxPower) { + return slope * dynamicPower + intercept; + } + } +} diff --git a/opendc-simulator/opendc-simulator-compute/src/test/kotlin/org/opendc/simulator/compute/ThermalTest.kt b/opendc-simulator/opendc-simulator-compute/src/test/kotlin/org/opendc/simulator/compute/ThermalTest.kt new file mode 100644 index 000000000..5018db145 --- /dev/null +++ b/opendc-simulator/opendc-simulator-compute/src/test/kotlin/org/opendc/simulator/compute/ThermalTest.kt @@ -0,0 +1,105 @@ +/* + * Copyright (c) 2024 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 kotlinx.coroutines.coroutineScope +import kotlinx.coroutines.launch +import kotlinx.coroutines.yield +import org.junit.jupiter.api.Assertions.assertAll +import org.junit.jupiter.api.Assertions.assertEquals +import org.junit.jupiter.api.BeforeEach +import org.junit.jupiter.api.Test +import org.opendc.simulator.compute.model.MachineModel +import org.opendc.simulator.compute.model.MemoryUnit +import org.opendc.simulator.compute.model.NetworkAdapter +import org.opendc.simulator.compute.model.ProcessingNode +import org.opendc.simulator.compute.model.ProcessingUnit +import org.opendc.simulator.compute.model.StorageDevice +import org.opendc.simulator.compute.power.CpuPowerModels +import org.opendc.simulator.compute.workload.SimWorkloads +import org.opendc.simulator.flow2.FlowEngine +import org.opendc.simulator.kotlin.runSimulation +import org.opendc.simulator.power.SimPowerSource + +class ThermalTest { + private val maxPower = 130.0 + private val idlePower = 12.0 + + private val powerModel = CpuPowerModels.linear(maxPower, idlePower) + private lateinit var machineModel: MachineModel + + @BeforeEach + fun setUp() { + val cpuNode = ProcessingNode("Intel", "i7-900", "amd64", 8) + + machineModel = + MachineModel( + List(cpuNode.coreCount) { ProcessingUnit(cpuNode, it, 3500.0) }, + List(4) { MemoryUnit("Crucial", "MTA18ASF4G72AZ-3G2B1", 3200.0, 32_000) }, + listOf(NetworkAdapter("Mellanox", "ConnectX-5", 25000.0)), + listOf(StorageDevice("Samsung", "EVO", 1000.0, 250.0, 250.0)), + ) + } + + private fun testUtilization( + utilization: Double, + expectedPower: Double, + expectedTemperature: Double, + ) = runSimulation { + val engine = FlowEngine.create(dispatcher) + val graph = engine.newGraph() + val machine = + SimBareMetalMachine.create( + graph, + machineModel, + SimPsuFactories.simple(powerModel), + ) + val source = SimPowerSource(graph, 1000.0f) + source.connect(machine.psu) + + coroutineScope { + launch { machine.runWorkload(SimWorkloads.flops(2_000, utilization)) } + + yield() + assertAll({ + assertEquals(expectedPower, machine.psu.thermalPower, 0.0001, "The power draw should be $expectedPower W") + assertEquals(expectedTemperature, machine.psu.cpuTemperature, 0.0001, "The temperature should be $expectedTemperature C") + }) + } + } + + @Test + fun zeroUtilization() = testUtilization(0.01, 24.0012, 45.48) + + @Test + fun quarterUtilization() = testUtilization(0.25, 53.5012, 51.085) + + @Test + fun halfUtilization() = testUtilization(0.50, 83.0012, 56.69) + + @Test + fun threeQuarterUtilization() = testUtilization(0.75, 112.5012, 62.295) + + @Test + fun fullUtilization() = testUtilization(1.0, 142.0012, 67.9) +} diff --git a/opendc-web/opendc-web-runner/src/main/kotlin/org/opendc/web/runner/OpenDCRunner.kt b/opendc-web/opendc-web-runner/src/main/kotlin/org/opendc/web/runner/OpenDCRunner.kt index 7eb6e21fe..37eb3c6b8 100644 --- a/opendc-web/opendc-web-runner/src/main/kotlin/org/opendc/web/runner/OpenDCRunner.kt +++ b/opendc-web/opendc-web-runner/src/main/kotlin/org/opendc/web/runner/OpenDCRunner.kt @@ -42,6 +42,7 @@ import org.opendc.simulator.compute.model.MemoryUnit import org.opendc.simulator.compute.model.ProcessingNode import org.opendc.simulator.compute.model.ProcessingUnit import org.opendc.simulator.compute.power.CpuPowerModels +import org.opendc.simulator.compute.thermal.ThermalModels import org.opendc.simulator.kotlin.runSimulation import org.opendc.web.proto.runner.Job import org.opendc.web.proto.runner.Scenario @@ -346,6 +347,15 @@ public class OpenDCRunner( val energyConsumptionW = machine.cpus.sumOf { it.energyConsumptionW } val powerModel = CpuPowerModels.linear(2 * energyConsumptionW, energyConsumptionW * 0.5) + val thermalModel = + ThermalModels.rcmodel( + 0.298, + 0.00061, + 0.00035, + 0.0041, + 1.8, + 22.0, + ) val spec = HostSpec( @@ -353,7 +363,7 @@ public class OpenDCRunner( "node-$clusterId-$position", mapOf("cluster" to clusterId), MachineModel(processors, memoryUnits), - SimPsuFactories.simple(powerModel), + SimPsuFactories.simple(powerModel, thermalModel), ) res += spec