Skip to content

Commit

Permalink
renamed MIPS to CU (compute unit)
Browse files Browse the repository at this point in the history
  • Loading branch information
birnbaum committed Dec 5, 2021
1 parent ef64fb3 commit da39814
Show file tree
Hide file tree
Showing 8 changed files with 91 additions and 88 deletions.
4 changes: 2 additions & 2 deletions README.md
Expand Up @@ -44,8 +44,8 @@ def place_task_after_2_seconds(env, node, task):
yield env.timeout(2)
task.allocate(node)

node = Node("node1", mips=100, power_model=PowerModelNode(max_power=30, static_power=10))
task = Task(mips=100)
node = Node("node1", cu=100, power_model=PowerModelNode(max_power=30, static_power=10))
task = Task(cu=100)

env = simpy.Environment()
# register our task placement process
Expand Down
4 changes: 2 additions & 2 deletions examples/1_single_node.py
Expand Up @@ -28,8 +28,8 @@ def main():
INFO Total power usage: 200.0 Ws
"""
# Initializing infrastructure and workload
node = Node("node1", mips=100, power_model=PowerModelNode(max_power=30, static_power=10))
task = Task(mips=100)
node = Node("node1", cu=100, power_model=PowerModelNode(max_power=30, static_power=10))
task = Task(cu=100)

measurements = []

Expand Down
30 changes: 15 additions & 15 deletions examples/2_application_placement.py
Expand Up @@ -26,9 +26,9 @@ def main():
Log Output:
INFO Placing Application(tasks=3):
INFO - SourceTask(id=0, mips=100) on Node('sensor', mips=0/1000).
INFO - ProcessingTask(id=1, mips=5000) on Node('fog', mips=0/400000).
INFO - SinkTask(id=2, mips=100) on Node('cloud', mips=0/inf).
INFO - SourceTask(id=0, cu=100) on Node('sensor', cu=0/1000).
INFO - ProcessingTask(id=1, cu=5000) on Node('fog', cu=0/400000).
INFO - SinkTask(id=2, cu=100) on Node('cloud', cu=0/inf).
INFO - DataFlow(bit_rate=1000) on [Link('sensor' -> 'fog', bandwidth=0/30000000.0, latency=10)].
INFO - DataFlow(bit_rate=200) on [Link('fog' -> 'cloud', bandwidth=0/1000000000.0, latency=5)].
DEBUG 0: cloud_and_fog_meter: PowerMeasurement(dynamic=70002.125W, static=30W)
Expand Down Expand Up @@ -68,19 +68,19 @@ def create_infrastructure():
"""Create the scenario's infrastructure graph.
It consists of three nodes:
- A sensor that can compute up to 1000 million instructions per second (MIPS).
- A sensor with a computational capacity of one compute unit (CU).
It has a maximum power usage of 1.8 Watt and a power usage of 0.2 Watt when being idle.
- A fog node which can compute up to 400000 MIPS; 200 Watt max and 30 Watt static power usage
- A node representing a cloud data center with unlimited processing power that consumes 700 W/MIPS
- A fog node which can compute up to 400 CU; 200 Watt max and 30 Watt static power usage
- A node representing a cloud data center with unlimited processing power that consumes 0.5 W/CU
And two network links that connect the nodes:
- A WiFi connection between the sensor and fog node that consumes 300 J/bit
- A wide are network (WAN) connection between the fog node and cloud that consumes 6000 J/bit
"""
infrastructure = Infrastructure()
sensor = Node("sensor", mips=1000, power_model=PowerModelNode(max_power=1.8, static_power=0.2))
fog_node = Node("fog", mips=400000, power_model=PowerModelNode(max_power=200, static_power=30))
cloud = Node("cloud", power_model=PowerModelNode(power_per_mips=700))
sensor = Node("sensor", cu=1, power_model=PowerModelNode(max_power=1.8, static_power=0.2))
fog_node = Node("fog", cu=400, power_model=PowerModelNode(max_power=200, static_power=30))
cloud = Node("cloud", power_model=PowerModelNode(power_per_cu=0.5))
wifi_link_up = Link(sensor, fog_node, latency=10, bandwidth=30e6, power_model=PowerModelLink(300))
wan_link_up = Link(fog_node, cloud, latency=5, bandwidth=1e9, power_model=PowerModelLink(6000))

Expand All @@ -93,16 +93,16 @@ def create_application(source_node: Node, sink_node: Node):
"""Create the application running in the scenario.
It consists of three tasks and two data flows between these tasks:
- A source task that is bound to the sensor node and requires 100 MIPS (for measuring data)
- A processing task that receives 1000 bit/s from the source task, requires 5000 MIPS (for aggregating the data)
- A source task that is bound to the sensor node and requires 0.1 CU (for measuring data)
- A processing task that receives 1000 bit/s from the source task, requires 5 CU (for aggregating the data)
and returns 200 bit/s to the sink task
- A sink task that is bound to the cloud node and requires 500 MIPS (for storing the data)
- A sink task that is bound to the cloud node and requires 0.5 CU (for storing the data)
"""
application = Application()

source_task = SourceTask(mips=100, bound_node=source_node)
processing_task = ProcessingTask(mips=5000)
sink_task = SinkTask(mips=100, bound_node=sink_node)
source_task = SourceTask(cu=0.1, bound_node=source_node)
processing_task = ProcessingTask(cu=5)
sink_task = SinkTask(cu=0.5, bound_node=sink_node)

application.add_task(source_task)
application.add_task(processing_task, incoming_data_flows=[(source_task, 1000)])
Expand Down
25 changes: 11 additions & 14 deletions examples/smart_city_traffic/infrastructure.py
Expand Up @@ -15,14 +15,14 @@

class Cloud(Node):
def __init__(self):
super().__init__("cloud", mips=CLOUD_MIPS, power_model=PowerModelNode(power_per_mips=CLOUD_WATT_PER_MIPS))
super().__init__("cloud", cu=CLOUD_CU, power_model=PowerModelNode(power_per_cu=CLOUD_WATT_PER_CU))


class FogNode(Node):
def __init__(self, location: "Location"):
# TODO Shutdown!
global _fog_nodes_created
super().__init__(f"fog_{_fog_nodes_created}", mips=FOG_MIPS,
super().__init__(f"fog_{_fog_nodes_created}", cu=FOG_CU,
power_model=PowerModelNode(max_power=FOG_MAX_POWER, static_power=FOG_STATIC_POWER))
_fog_nodes_created += 1
self.location = location
Expand All @@ -41,36 +41,33 @@ def add_task(self, task: "Task"):

def remove_task(self, task: "Task"):
super().remove_task(task)
if FOG_IDLE_SHUTDOWN and self.used_mips == 0:
if FOG_IDLE_SHUTDOWN and self.used_cu == 0:
self.shutdown = True





class TrafficLight(Node):
def __init__(self, location: "Location", application_sink: Node):
global _traffic_lights_created
super().__init__(f"traffic_light_{_traffic_lights_created}", mips=0, power_model=PowerModelNode(0, 0))
super().__init__(f"traffic_light_{_traffic_lights_created}", cu=0, power_model=PowerModelNode(0, 0))
_traffic_lights_created += 1
self.location = location
self.application = self._create_cctv_application(application_sink)

def _create_cctv_application(self, application_sink: Node):
application = Application()
source_task = SourceTask(mips=0, bound_node=self)
source_task = SourceTask(cu=0, bound_node=self)
application.add_task(source_task)
processing_task = ProcessingTask(mips=CCTV_PROCESSOR_MIPS)
processing_task = ProcessingTask(cu=CCTV_PROCESSOR_CU)
application.add_task(processing_task, incoming_data_flows=[(source_task, CCTV_SOURCE_TO_PROCESSOR_BIT_RATE)])
sink_task = SinkTask(mips=0, bound_node=application_sink)
sink_task = SinkTask(cu=0, bound_node=application_sink)
application.add_task(sink_task, incoming_data_flows=[(processing_task, CCTV_PROCESSOR_TO_SINK_BIT_RATE)])
return application


class Taxi(Node):
def __init__(self, env: simpy.Environment, mobility_model: "TaxiMobilityModel", application_sinks: List[Node]):
global _taxis_created
super().__init__(f"taxi_{_taxis_created}", mips=0, power_model=PowerModelNode(0, 0))
super().__init__(f"taxi_{_taxis_created}", cu=0, power_model=PowerModelNode(0, 0))
_taxis_created += 1
self.env = env
self.application = self._create_v2i_application(application_sinks)
Expand All @@ -82,12 +79,12 @@ def location(self) -> "Location":

def _create_v2i_application(self, application_sinks: List[Node]) -> Application:
application = Application()
source_task = SourceTask(mips=0, bound_node=self)
source_task = SourceTask(cu=0, bound_node=self)
application.add_task(source_task)
processing_task = ProcessingTask(mips=V2I_PROCESSOR_MIPS)
processing_task = ProcessingTask(cu=V2I_PROCESSOR_CU)
application.add_task(processing_task, incoming_data_flows=[(source_task, V2I_SOURCE_TO_PROCESSOR_BIT_RATE)])
for application_sink in application_sinks:
sink_task = SinkTask(mips=0, bound_node=application_sink)
sink_task = SinkTask(cu=0, bound_node=application_sink)
application.add_task(sink_task, incoming_data_flows=[(processing_task, V2I_PROCESSOR_TO_SINK_BIT_RATE)])
return application

Expand Down
10 changes: 5 additions & 5 deletions examples/smart_city_traffic/settings.py

Large diffs are not rendered by default.

31 changes: 17 additions & 14 deletions leaf/application.py
Expand Up @@ -8,20 +8,23 @@


class Task(PowerAware):
def __init__(self, mips: float):
def __init__(self, cu: float):
"""Task that can be placed on a :class:`Node`.
Tasks _can_ be connected via :class:`Link`s to build an :class:`Application`.
Args:
mips: Million instructions per second required to execute the task.
cu: Amount of compute units (CU) required to execute the task. CUs a imaginary unit for computational
power to express differences between hardware platforms.
Million instructions per second required to execute the task.
"""
self.id: Optional[int] = None
self.mips = mips
self.cu = cu
self.node: Optional[Node] = None

def __repr__(self):
return f"{self.__class__.__name__}(id={self.id}, mips={self.mips})"
return f"{self.__class__.__name__}(id={self.id}, cu={self.cu})"

def allocate(self, node: Node):
"""Place the task on a certain node and allocate resources."""
Expand All @@ -39,48 +42,48 @@ def deallocate(self):

def measure_power(self) -> PowerMeasurement:
try:
return self.node.measure_power().multiply(self.mips / self.node.used_mips)
return self.node.measure_power().multiply(self.cu / self.node.used_cu)
except ZeroDivisionError:
return PowerMeasurement(0, 0)


class SourceTask(Task):
def __init__(self, mips: float = 0, bound_node: Node = None):
def __init__(self, cu: float = 0, bound_node: Node = None):
"""Source task of an application that is bound to a certain node, e.g. a sensor generating data.
Source tasks never have incoming and always have outgoing data flows.
Args:
mips: Million instructions per second required to execute the task.
cu: Million instructions per second required to execute the task.
bound_node: The node which the task is bound to. Cannot be None.
"""
super().__init__(mips)
super().__init__(cu)
if bound_node is None:
raise ValueError("bound_node for SourceTask cannot be None")
self.bound_node = bound_node


class ProcessingTask(Task):
def __init__(self, mips: float = 0):
def __init__(self, cu: float = 0):
"""Processing task of an application that can be freely placed on the infrastructure.
Processing tasks always have incoming and outgoing data flows.
Args:
mips: Million instructions per second required to execute the task.
cu: Million instructions per second required to execute the task.
"""
super().__init__(mips)
super().__init__(cu)


class SinkTask(Task):
def __init__(self, mips: float = 0, bound_node: Node = None):
def __init__(self, cu: float = 0, bound_node: Node = None):
"""Sink task of an application that is bound to a certain node, e.g. a cloud server for storage.
Args:
mips: Million instructions per second required to execute the task.
cu: Million instructions per second required to execute the task.
bound_node: The node which the task is bound to. Cannot be None.
"""
super().__init__(mips)
super().__init__(cu)
if bound_node is None:
raise ValueError("bound_node for SourceTask cannot be None")
self.bound_node = bound_node
Expand Down
53 changes: 28 additions & 25 deletions leaf/infrastructure.py
Expand Up @@ -8,8 +8,8 @@

class Node(PowerAware):
def __init__(self, name: str,
mips: Optional[float] = None,
power_model: Optional["PowerModel"] = None):
cu: Optional[float] = None,
power_model: Optional["PowerModelNode"] = None):
"""A compute node in the infrastructure graph.
This can represent any kind of node, e.g.
Expand All @@ -20,48 +20,51 @@ def __init__(self, name: str,
Args:
name: Name of the node. This is used to refer to nodes when defining links.
mips: Maximum processing power the node provides in "million instructions per second".
If None, the node has unlimited processing power.
cu: Maximum processing power the node provides in "compute units", a imaginary unit for computational power
to express differences between hardware platforms. If None, the node has unlimited processing power.
power_model: Power model which determines the power usage of the node.
"""
self.name = name
if mips is None:
self.mips = math.inf
if cu is None:
self.cu = math.inf
else:
self.mips = mips
self.used_mips = 0
self.cu = cu
self.used_cu = 0
self.tasks: List["Task"] = []

if power_model:
if cu is None and power_model.max_power is not None:
raise ValueError("Cannot use PowerModelNode with `max_power` on a compute node with unlimited "
"processing power")
self.power_model = power_model
self.power_model.set_parent(self)

def __repr__(self):
mips_repr = self.mips if self.mips is not None else "∞"
return f"{self.__class__.__name__}('{self.name}', mips={self.used_mips}/{mips_repr})"
cu_repr = self.cu if self.cu is not None else "∞"
return f"{self.__class__.__name__}('{self.name}', cu={self.used_cu}/{cu_repr})"

def utilization(self) -> float:
"""Return the current utilization of the resource in the range [0, 1]."""
try:
return self.used_mips / self.mips
return self.used_cu / self.cu
except ZeroDivisionError:
assert self.used_mips == 0
assert self.used_cu == 0
return 0

def _add_task(self, task: "Task"):
"""Add a task to the node.
Private as this is only called by leaf.application.Task and not part of the public interface.
"""
self._reserve_mips(task.mips)
self._reserve_cu(task.cu)
self.tasks.append(task)

def _remove_task(self, task: "Task"):
"""Remove a task from the node.
Private as this is only called by leaf.application.Task and not part of the public interface.
"""
self._release_mips(task.mips)
self._release_cu(task.cu)
self.tasks.remove(task)

def measure_power(self) -> PowerMeasurement:
Expand All @@ -70,17 +73,17 @@ def measure_power(self) -> PowerMeasurement:
except TypeError:
raise RuntimeError(f"{self} has no power model.")

def _reserve_mips(self, mips: float):
new_used_mips = self.used_mips + mips
if new_used_mips > self.mips:
raise ValueError(f"Cannot reserve {mips} mips on compute node {self}.")
self.used_mips = new_used_mips

def _release_mips(self, mips: float):
new_used_mips = self.used_mips - mips
if new_used_mips < 0:
raise ValueError(f"Cannot release {mips} mips on compute node {self}.")
self.used_mips = new_used_mips
def _reserve_cu(self, cu: float):
new_used_cu = self.used_cu + cu
if new_used_cu > self.cu:
raise ValueError(f"Cannot reserve {cu} CU on compute node {self}.")
self.used_cu = new_used_cu

def _release_cu(self, cu: float):
new_used_cu = self.used_cu - cu
if new_used_cu < 0:
raise ValueError(f"Cannot release {cu} CU on compute node {self}.")
self.used_cu = new_used_cu


class Link(PowerAware):
Expand Down
22 changes: 11 additions & 11 deletions leaf/power.py
Expand Up @@ -72,7 +72,7 @@ def set_parent(self, parent):


class PowerModelNode(PowerModel):
def __init__(self, max_power: float = None, power_per_mips: float = None, static_power: float = 0):
def __init__(self, max_power: float = None, power_per_cu: float = None, static_power: float = 0):
"""Power model for compute nodes with static and dynamic power usage.
Power usage is scaled linearly with resource usage.
Expand All @@ -82,26 +82,26 @@ def __init__(self, max_power: float = None, power_per_mips: float = None, static
up to 150 Watt when under full load (`max_power=150`).
Args:
max_power: Maximum power usage of the node under full load. Cannot be combined with `power_per_mips`.
power_per_mips: Incremental power usage for each mips. Cannot be combined with `max_power`.
max_power: Maximum power usage of the node under full load. Cannot be combined with `power_per_cu`.
power_per_cu: Incremental power usage for each used compute unit. Cannot be combined with `max_power`.
static_power: Idle power usage of the node without any load.
"""
if max_power is None and power_per_mips is None:
raise ValueError("Either `max_power` or `power_per_mips` have to be stated.")
if max_power is not None and power_per_mips is not None:
raise ValueError("The parameters `max_power` or `power_per_mips` cannot be combined.")
if max_power is None and power_per_cu is None:
raise ValueError("Either `max_power` or `power_per_cu` have to be stated.")
if max_power is not None and power_per_cu is not None:
raise ValueError("The parameters `max_power` or `power_per_cu` cannot be combined.")
self.max_power = max_power
self.power_per_mips = power_per_mips
self.power_per_cu = power_per_cu
self.static_power = static_power
self.node = None

def measure(self) -> PowerMeasurement:
if self.max_power is not None:
dynamic_power = (self.max_power - self.static_power) * self.node.utilization()
elif self.power_per_mips is not None:
dynamic_power = self.power_per_mips * self.node.used_mips
elif self.power_per_cu is not None:
dynamic_power = self.power_per_cu * self.node.used_cu
else:
raise RuntimeError("Invalid state of PowerModelNode: `max_power` and `power_per_mips` are undefined.")
raise RuntimeError("Invalid state of PowerModelNode: `max_power` and `power_per_cu` are undefined.")
return PowerMeasurement(dynamic=dynamic_power, static=self.static_power)

def set_parent(self, parent):
Expand Down

0 comments on commit da39814

Please sign in to comment.