diff --git a/examples/1_single_node.py b/examples/1_single_node.py index 3ce9a41..8a1466f 100644 --- a/examples/1_single_node.py +++ b/examples/1_single_node.py @@ -3,7 +3,7 @@ from leaf.application import Task from leaf.infrastructure import Node -from leaf.power import PowerModelNode, PowerMeasurement, power_meter +from leaf.power import PowerModelNode, PowerMeasurement, PowerMeter logger = logging.getLogger(__name__) logging.basicConfig(level=logging.DEBUG, format='%(levelname)s\t%(message)s') @@ -13,34 +13,33 @@ def main(): """Simple example that adds and removes a task from a single node Log Output: - DEBUG 0: PowerMeter1: PowerMeasurement(dynamic=0.0W, static=10W) - DEBUG 1: PowerMeter1: PowerMeasurement(dynamic=0.0W, static=10W) - DEBUG 2: PowerMeter1: PowerMeasurement(dynamic=0.0W, static=10W) + DEBUG 0: PowerMeter1: PowerMeasurement(dynamic=0.00W, static=10.00W) + DEBUG 1: PowerMeter1: PowerMeasurement(dynamic=0.00W, static=10.00W) + DEBUG 2: PowerMeter1: PowerMeasurement(dynamic=0.00W, static=10.00W) INFO task has been added at 3 - DEBUG 3: PowerMeter1: PowerMeasurement(dynamic=20.0W, static=10W) - DEBUG 4: PowerMeter1: PowerMeasurement(dynamic=20.0W, static=10W) - DEBUG 5: PowerMeter1: PowerMeasurement(dynamic=20.0W, static=10W) - DEBUG 6: PowerMeter1: PowerMeasurement(dynamic=20.0W, static=10W) - DEBUG 7: PowerMeter1: PowerMeasurement(dynamic=20.0W, static=10W) + DEBUG 3: PowerMeter1: PowerMeasurement(dynamic=20.00W, static=10.00W) + DEBUG 4: PowerMeter1: PowerMeasurement(dynamic=20.00W, static=10.00W) + DEBUG 5: PowerMeter1: PowerMeasurement(dynamic=20.00W, static=10.00W) + DEBUG 6: PowerMeter1: PowerMeasurement(dynamic=20.00W, static=10.00W) + DEBUG 7: PowerMeter1: PowerMeasurement(dynamic=20.00W, static=10.00W) INFO task has been removed at 8 - DEBUG 8: PowerMeter1: PowerMeasurement(dynamic=0.0W, static=10W) - DEBUG 9: PowerMeter1: PowerMeasurement(dynamic=0.0W, static=10W) + DEBUG 8: PowerMeter1: PowerMeasurement(dynamic=0.00W, static=10.00W) + DEBUG 9: PowerMeter1: PowerMeasurement(dynamic=0.00W, static=10.00W) INFO Total power usage: 200.0 Ws """ # Initializing infrastructure and workload node = Node("node1", cu=100, power_model=PowerModelNode(max_power=30, static_power=10)) task = Task(cu=100) - measurements = [] + power_meter = PowerMeter(node, name="PowerMeter1") env = simpy.Environment() # creating SimPy simulation environment env.process(placement(env, node, task)) # registering workload placement process - env.process(power_meter(env, node, name="PowerMeter1", - callback=lambda m: measurements.append(m))) # registering power metering process + env.process(power_meter.run(env)) # registering power metering process env.run(until=10) # run simulation for 10 seconds - logger.info(f"Total power usage: {float(PowerMeasurement.sum(measurements))} Ws") + logger.info(f"Total power usage: {float(PowerMeasurement.sum(power_meter.measurements))} Ws") def placement(env, node, task): diff --git a/examples/2_application_placement.py b/examples/2_application_placement.py index c5c77b3..31a7d85 100644 --- a/examples/2_application_placement.py +++ b/examples/2_application_placement.py @@ -5,7 +5,7 @@ from leaf.application import Application, SourceTask, ProcessingTask, SinkTask from leaf.infrastructure import Node, Link, Infrastructure from leaf.orchestrator import Orchestrator -from leaf.power import PowerModelNode, PowerModelLink, power_meter +from leaf.power import PowerModelNode, PowerModelLink, PowerMeter RANDOM_SEED = 1 @@ -26,41 +26,38 @@ def main(): Log Output: INFO Placing Application(tasks=3): - 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 - SourceTask(id=0, cu=0.1) on Node('sensor', cu=0/1). + INFO - ProcessingTask(id=1, cu=5) on Node('fog', cu=0/400). + INFO - SinkTask(id=2, cu=0.5) 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) - DEBUG 0: infrastructure_meter: PowerMeasurement(dynamic=1570002.2850000001W, static=30.2W) - DEBUG 0.5: application_meter: PowerMeasurement(dynamic=1570002.2850000001W, static=30.2W) - DEBUG 1: cloud_and_fog_meter: PowerMeasurement(dynamic=70002.125W, static=30W) - DEBUG 1.5: application_meter: PowerMeasurement(dynamic=1570002.2850000001W, static=30.2W) - DEBUG 2: infrastructure_meter: PowerMeasurement(dynamic=1570002.2850000001W, static=30.2W) - DEBUG 2: cloud_and_fog_meter: PowerMeasurement(dynamic=70002.125W, static=30W) - DEBUG 2.5: application_meter: PowerMeasurement(dynamic=1570002.2850000001W, static=30.2W) - DEBUG 3: cloud_and_fog_meter: PowerMeasurement(dynamic=70002.125W, static=30W) - DEBUG 3.5: application_meter: PowerMeasurement(dynamic=1570002.2850000001W, static=30.2W) - DEBUG 4: infrastructure_meter: PowerMeasurement(dynamic=1570002.2850000001W, static=30.2W) - DEBUG 4: cloud_and_fog_meter: PowerMeasurement(dynamic=70002.125W, static=30W) - DEBUG 4.5: application_meter: PowerMeasurement(dynamic=1570002.2850000001W, static=30.2W) + DEBUG 0: cloud_and_fog_meter: PowerMeasurement(dynamic=2.38W, static=30.00W) + DEBUG 0: infrastructure_meter: PowerMeasurement(dynamic=2.54W, static=30.20W) + DEBUG 0.5: application_meter: PowerMeasurement(dynamic=2.54W, static=30.20W) + DEBUG 1: cloud_and_fog_meter: PowerMeasurement(dynamic=2.38W, static=30.00W) + DEBUG 1.5: application_meter: PowerMeasurement(dynamic=2.54W, static=30.20W) + DEBUG 2: infrastructure_meter: PowerMeasurement(dynamic=2.54W, static=30.20W) + DEBUG 2: cloud_and_fog_meter: PowerMeasurement(dynamic=2.38W, static=30.00W) + DEBUG 2.5: application_meter: PowerMeasurement(dynamic=2.54W, static=30.20W) + DEBUG 3: cloud_and_fog_meter: PowerMeasurement(dynamic=2.38W, static=30.00W) + DEBUG 3.5: application_meter: PowerMeasurement(dynamic=2.54W, static=30.20W) + DEBUG 4: infrastructure_meter: PowerMeasurement(dynamic=2.54W, static=30.20W) + DEBUG 4: cloud_and_fog_meter: PowerMeasurement(dynamic=2.38W, static=30.00W) + DEBUG 4.5: application_meter: PowerMeasurement(dynamic=2.54W, static=30.20W) """ infrastructure = create_infrastructure() application = create_application(source_node=infrastructure.node("sensor"), sink_node=infrastructure.node("cloud")) orchestrator = SimpleOrchestrator(infrastructure) orchestrator.place(application) - application_measurements = [] - cloud_and_fog_measurements = [] - infrastructure_measurements = [] + application_pm = PowerMeter(application, name="application_meter") + cloud_and_fog_pm = PowerMeter([infrastructure.node("cloud"), infrastructure.node("fog")], name="cloud_and_fog_meter") + infrastructure_pm = PowerMeter(infrastructure, name="infrastructure_meter", measurement_interval=2) env = simpy.Environment() - env.process(power_meter(env, application, name="application_meter", delay=0.5, - callback=lambda m: application_measurements.append(m))) - env.process(power_meter(env, [infrastructure.node("cloud"), infrastructure.node("fog")], name="cloud_and_fog_meter", - callback=lambda m: cloud_and_fog_measurements.append(m))) - env.process(power_meter(env, infrastructure, name="infrastructure_meter", measurement_interval=2, - callback=lambda m: infrastructure_measurements.append(m))) + env.process(application_pm.run(env, delay=0.5)) + env.process(cloud_and_fog_pm.run(env)) + env.process(infrastructure_pm.run(env)) env.run(until=5) @@ -81,8 +78,8 @@ def create_infrastructure(): 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)) + wifi_link_up = Link(sensor, fog_node, latency=10, bandwidth=30e6, power_model=PowerModelLink(300e-9)) + wan_link_up = Link(fog_node, cloud, latency=5, bandwidth=1e9, power_model=PowerModelLink(6000e-9)) infrastructure.add_link(wifi_link_up) infrastructure.add_link(wan_link_up) diff --git a/examples/smart_city_traffic/city.py b/examples/smart_city_traffic/city.py index 6ac2c12..d875042 100644 --- a/examples/smart_city_traffic/city.py +++ b/examples/smart_city_traffic/city.py @@ -5,7 +5,7 @@ from examples.smart_city_traffic.infrastructure import Cloud, FogNode, TrafficLight, LinkWanUp, LinkEthernet, \ LinkWifiBetweenTrafficLights, LinkWanDown, LinkWifiTaxiToTrafficLight, Taxi -from examples.smart_city_traffic.mobility import Location +from mobility import Location from examples.smart_city_traffic.orchestrator import CityOrchestrator from examples.smart_city_traffic.settings import * from leaf.infrastructure import Infrastructure diff --git a/examples/smart_city_traffic/infrastructure.py b/examples/smart_city_traffic/infrastructure.py index c19beeb..7dc02fd 100644 --- a/examples/smart_city_traffic/infrastructure.py +++ b/examples/smart_city_traffic/infrastructure.py @@ -23,9 +23,9 @@ def __init__(self, location: "Location"): # TODO Shutdown! global _fog_nodes_created super().__init__(f"fog_{_fog_nodes_created}", cu=FOG_CU, - power_model=PowerModelNode(max_power=FOG_MAX_POWER, static_power=FOG_STATIC_POWER)) + power_model=PowerModelNode(max_power=FOG_MAX_POWER, static_power=FOG_STATIC_POWER), + location=location) _fog_nodes_created += 1 - self.location = location self.shutdown = FOG_IDLE_SHUTDOWN def measure_power(self) -> PowerMeasurement: @@ -48,9 +48,8 @@ def remove_task(self, task: "Task"): class TrafficLight(Node): def __init__(self, location: "Location", application_sink: Node): global _traffic_lights_created - super().__init__(f"traffic_light_{_traffic_lights_created}", cu=0, power_model=PowerModelNode(0, 0)) + super().__init__(f"traffic_light_{_traffic_lights_created}", location=location) _traffic_lights_created += 1 - self.location = location self.application = self._create_cctv_application(application_sink) def _create_cctv_application(self, application_sink: Node): @@ -67,7 +66,7 @@ def _create_cctv_application(self, application_sink: Node): 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}", cu=0, power_model=PowerModelNode(0, 0)) + super().__init__(f"taxi_{_taxis_created}") _taxis_created += 1 self.env = env self.application = self._create_v2i_application(application_sinks) @@ -77,6 +76,10 @@ def __init__(self, env: simpy.Environment, mobility_model: "TaxiMobilityModel", def location(self) -> "Location": return self.mobility_model.location(self.env.now) + @location.setter + def location(self, value): + pass # only for initialization, locations of this node is managed by the TaxiMobilityModel + def _create_v2i_application(self, application_sinks: List[Node]) -> Application: application = Application() source_task = SourceTask(cu=0, bound_node=self) diff --git a/examples/smart_city_traffic/main.py b/examples/smart_city_traffic/main.py index c608595..772d7b9 100755 --- a/examples/smart_city_traffic/main.py +++ b/examples/smart_city_traffic/main.py @@ -11,7 +11,7 @@ from examples.smart_city_traffic.settings import SIMULATION_TIME, FOG_DCS, POWER_MEASUREMENT_INTERVAL, \ FOG_IDLE_SHUTDOWN from leaf.infrastructure import Infrastructure -from leaf.power import power_meter +from leaf.power import PowerMeter logger = logging.getLogger(__name__) logging.basicConfig(level=logging.WARN, format='%(levelname)s: %(message)s') @@ -21,7 +21,8 @@ def main(count_taxis: bool, measure_infrastructure: bool, measure_applications: # ----------------- Set up experiment ----------------- env = simpy.Environment() city = City(env) - MobilityManager(env, city) + mobility_manager = MobilityManager(city) + env.process(mobility_manager.run(env)) # ----------------- Initialize meters ----------------- if count_taxis: @@ -29,16 +30,23 @@ def main(count_taxis: bool, measure_infrastructure: bool, measure_applications: taxi_counter = TaxiCounter(env, city.infrastructure) if measure_infrastructure: # Measures the power usage of cloud and fog nodes as well as WAN and WiFi links - pm_cloud = _PowerMeter(env, entities=city.infrastructure.nodes(type_filter=Cloud), name="cloud") - pm_fog = _PowerMeter(env, entities=city.infrastructure.nodes(type_filter=FogNode), name="fog") - pm_wan_up = _PowerMeter(env, entities=city.infrastructure.links(type_filter=LinkWanUp), name="wan_up") - pm_wan_down = _PowerMeter(env, entities=city.infrastructure.links(type_filter=LinkWanDown), name="wan_down") - pm_wifi = _PowerMeter(env, entities=lambda: city.infrastructure.links( - type_filter=(LinkWifiBetweenTrafficLights, LinkWifiTaxiToTrafficLight)), name="wifi") + pm_cloud = PowerMeter(entities=city.infrastructure.nodes(type_filter=Cloud), name="cloud", measurement_interval=POWER_MEASUREMENT_INTERVAL) + pm_fog = PowerMeter(entities=city.infrastructure.nodes(type_filter=FogNode), name="fog", measurement_interval=POWER_MEASUREMENT_INTERVAL) + pm_wan_up = PowerMeter(entities=city.infrastructure.links(type_filter=LinkWanUp), name="wan_up", measurement_interval=POWER_MEASUREMENT_INTERVAL) + pm_wan_down = PowerMeter(entities=city.infrastructure.links(type_filter=LinkWanDown), name="wan_down", measurement_interval=POWER_MEASUREMENT_INTERVAL) + pm_wifi = PowerMeter(entities=lambda: city.infrastructure.links(type_filter=(LinkWifiBetweenTrafficLights, LinkWifiTaxiToTrafficLight)), name="wifi", measurement_interval=POWER_MEASUREMENT_INTERVAL) + + env.process(pm_cloud.run(env)) + env.process(pm_fog.run(env)) + env.process(pm_wan_up.run(env)) + env.process(pm_wan_down.run(env)) + env.process(pm_wifi.run(env)) if measure_applications: # Measures the power usage of the V2I and CCTV applications - pm_v2i = _PowerMeter(env, entities=lambda: [taxi.application for taxi in city.infrastructure.nodes(type_filter=Taxi)], name="v2i") - pm_cctv = _PowerMeter(env, entities=lambda: [tl.application for tl in city.infrastructure.nodes(type_filter=TrafficLight)], name="cctv") + pm_v2i = PowerMeter(entities=lambda: [taxi.application for taxi in city.infrastructure.nodes(type_filter=Taxi)], name="v2i", measurement_interval=POWER_MEASUREMENT_INTERVAL) + pm_cctv = PowerMeter(entities=lambda: [tl.application for tl in city.infrastructure.nodes(type_filter=TrafficLight)], name="cctv", measurement_interval=POWER_MEASUREMENT_INTERVAL) + env.process(pm_v2i.run(env)) + env.process(pm_cctv.run(env)) # ------------------ Run experiment ------------------- for until in tqdm(range(1, SIMULATION_TIME)): @@ -70,13 +78,6 @@ def main(count_taxis: bool, measure_infrastructure: bool, measure_applications: csvfile.write(csv_content) -class _PowerMeter: - def __init__(self, env, entities, **kwargs): - self.measurements = [] - env.process(power_meter(env, entities, measurement_interval=POWER_MEASUREMENT_INTERVAL, - callback=lambda m: self.measurements.append(m), **kwargs)) - - class TaxiCounter: def __init__(self, env: simpy.Environment, infrastructure: Infrastructure): self.env = env diff --git a/examples/smart_city_traffic/mobility.py b/examples/smart_city_traffic/mobility.py index d56893f..e60d385 100644 --- a/examples/smart_city_traffic/mobility.py +++ b/examples/smart_city_traffic/mobility.py @@ -1,4 +1,3 @@ -import math from typing import List, Optional import networkx as nx @@ -7,55 +6,39 @@ from examples.smart_city_traffic.infrastructure import TrafficLight, Taxi from examples.smart_city_traffic.settings import UPDATE_MOBILITY_INTERVAL, MAX_CARS_PER_MINUTE, RNG, \ TAXI_COUNT_DISTRIBUTION, TAXI_SPEED_DISTRIBUTION - - -class Location: - def __init__(self, x: float, y: float): - self.x = x - self.y = y - - def distance(self, location: "Location") -> float: - return math.sqrt((location.y - self.y) * (location.y - self.y) + (location.x - self.x) * (location.x - self.x)) - - def __eq__(self, other): - return self.x == other.x and self.y == other.y - - def __hash__(self): - return hash((self.x, self.y)) +from leaf.mobility import Location class MobilityManager: - def __init__(self, env: simpy.Environment, city: "City"): - self.env = env + def __init__(self, city: "City"): self.city = city - self.add_taxis_process = env.process(self._add_taxis_process()) - def _add_taxis_process(self): + def run(self, env: simpy.Environment): while True: - for taxi in self._create_taxis(): + for taxi in self._create_taxis(env): self.city.add_taxi_and_start_v2i_app(taxi) - self.env.process(self._remove_taxi_process(taxi)) - yield self.env.timeout(UPDATE_MOBILITY_INTERVAL) + env.process(self._remove_taxi_process(env, taxi)) + yield env.timeout(UPDATE_MOBILITY_INTERVAL) - def _remove_taxi_process(self, taxi: "Taxi"): - yield self.env.timeout(taxi.mobility_model.life_time) + def _remove_taxi_process(self, env: simpy.Environment, taxi: "Taxi"): + yield env.timeout(taxi.mobility_model.life_time) self.city.remove_taxi_and_stop_v2i_app(taxi) - def _create_taxis(self) -> List["Taxi"]: - avg_taxi_speed = _avg_taxi_speed(self.env.now) - avg_taxi_count = _avg_taxi_count(self.env.now) + def _create_taxis(self, env: simpy.Environment) -> List["Taxi"]: + avg_taxi_speed = _avg_taxi_speed(env.now) + avg_taxi_count = _avg_taxi_count(env.now) taxi_count = RNG.poisson(avg_taxi_count) - return [self._create_taxi(speed=avg_taxi_speed) for _ in range(taxi_count)] + return [self._create_taxi(env=env, speed=avg_taxi_speed) for _ in range(taxi_count)] - def _create_taxi(self, speed: float) -> "Taxi": + def _create_taxi(self, env: simpy.Environment, speed: float) -> "Taxi": start = self._random_gate_location() dst = self._random_gate_location() while not start.distance(dst) > 0.5: dst = self._random_gate_location() path = nx.shortest_path(self.city.street_graph, source=start, target=dst) - mobility_model = TaxiMobilityModel(path, speed=speed, start_time=self.env.now) - return Taxi(self.env, mobility_model, application_sinks=self._traffic_lights_on_taxi_path(path)) + mobility_model = TaxiMobilityModel(path, speed=speed, start_time=env.now) + return Taxi(env, mobility_model, application_sinks=self._traffic_lights_on_taxi_path(path)) def _random_gate_location(self) -> Location: return RNG.choice(self.city.entry_point_locations) @@ -63,21 +46,6 @@ def _random_gate_location(self) -> Location: def _traffic_lights_on_taxi_path(self, path: List) -> List[TrafficLight]: return [tl for tl in self.city.infrastructure.nodes(type_filter=TrafficLight) if tl.location in path] - # /** - # * Calculates which traffic light systems are in the path of the taxi. - # */ - # private static List getTrafficLightSystemsOnPath(Taxi taxi) { - # List locations = taxi.getMobilityModel().getPath().getVertexList(); - # locations.remove(locations.size() - 1); - # locations.remove(0); - # Set locationSet = new HashSet<>(locations); - # - # InfrastructureGraphCity topologyExp = (InfrastructureGraphCity) taxi.getSimulation().getNetworkTopology(); - # return topologyExp.getTraficLightSystems().stream() - # .filter(tls -> locationSet.contains(tls.getLocation())) - # .collect(Collectors.toList()); - # } - class TaxiMobilityModel: def __init__(self, path: List[Location], speed: float, start_time: float): diff --git a/leaf/infrastructure.py b/leaf/infrastructure.py index 3b28c87..4cdc8c8 100755 --- a/leaf/infrastructure.py +++ b/leaf/infrastructure.py @@ -4,12 +4,14 @@ import networkx as nx from leaf.power import PowerAware, PowerMeasurement +from leaf.mobility import Location class Node(PowerAware): def __init__(self, name: str, cu: Optional[float] = None, - power_model: Optional["PowerModelNode"] = None): + power_model: Optional["PowerModelNode"] = None, + location: Optional[Location] = None): """A compute node in the infrastructure graph. This can represent any kind of node, e.g. @@ -23,6 +25,7 @@ def __init__(self, name: str, 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. + location: The (x,y) coordinates of the node """ self.name = name if cu is None: @@ -39,6 +42,8 @@ def __init__(self, name: str, self.power_model = power_model self.power_model.set_parent(self) + self.location = location + def __repr__(self): cu_repr = self.cu if self.cu is not None else "∞" return f"{self.__class__.__name__}('{self.name}', cu={self.used_cu}/{cu_repr})" @@ -70,7 +75,7 @@ def _remove_task(self, task: "Task"): def measure_power(self) -> PowerMeasurement: try: return self.power_model.measure() - except TypeError: + except AttributeError: raise RuntimeError(f"{self} has no power model.") def _reserve_cu(self, cu: float): @@ -136,7 +141,7 @@ def _remove_data_flow(self, data_flow: "DataFlow"): def measure_power(self) -> PowerMeasurement: try: return self.power_model.measure() - except TypeError: + except AttributeError: raise RuntimeError(f"{self} has no power model.") def _reserve_bandwidth(self, bandwidth): diff --git a/leaf/mobility.py b/leaf/mobility.py new file mode 100644 index 0000000..f9b3448 --- /dev/null +++ b/leaf/mobility.py @@ -0,0 +1,16 @@ +import math + + +class Location: + def __init__(self, x: float, y: float): + self.x = x + self.y = y + + def distance(self, location: "Location") -> float: + return math.sqrt((location.y - self.y) * (location.y - self.y) + (location.x - self.x) * (location.x - self.x)) + + def __eq__(self, other): + return self.x == other.x and self.y == other.y + + def __hash__(self): + return hash((self.x, self.y)) diff --git a/leaf/power.py b/leaf/power.py index 2bed861..b2961d0 100644 --- a/leaf/power.py +++ b/leaf/power.py @@ -27,7 +27,7 @@ def sum(cls, measurements: Iterable["PowerMeasurement"]): return PowerMeasurement(dynamic, static) def __repr__(self): - return f"PowerMeasurement(dynamic={self.dynamic}W, static={self.static}W)" + return f"PowerMeasurement(dynamic={self.dynamic:.2f}W, static={self.static:.2f}W)" def __float__(self) -> float: return float(self.dynamic + self.static) @@ -166,49 +166,51 @@ def measure_power(self) -> PowerMeasurement: class PowerMeter: - """Convenience class that wraps power_meter().""" - def __init__(self, env, entities, **kwargs): - self.measurements = [] - env.process(power_meter(env, entities, callback=lambda m: self.measurements.append(m), **kwargs)) - - -def power_meter(env: simpy.Environment, - entities: Union[PowerAware, Collection[PowerAware], Callable[[], Collection[PowerAware]]], - callback: Callable[[PowerMeasurement], None], - name: Optional[str] = None, - measurement_interval: Optional[float] = 1, - delay: Optional[float] = 0): - """Power meter with measures and saves the power of one or more entites in regular intervals. + """Power meter that stores the power of one or more entites in regular intervals. Args: - env: Simpy environment (for timing the measurements) entities: Can be either (1) a single :class:`PowerAware` entity (2) a list of :class:`PowerAware` entities (3) a function which returns a list of :class:`PowerAware` entities, if the number of these entities changes during the simulation. - callback: TODO name: Name of the power meter for logging and reporting measurement_interval: The measurement interval. - delay: The delay after which the measurements shall be conducted. For some scenarios it makes sense to e.g. - include a tiny delay to make sure that all events at a previous time step were processed before the - measurement is conducted. """ - if name is None: - global _unnamed_power_meters_created - name = f"power_meter_{_unnamed_power_meters_created}" - _unnamed_power_meters_created += 1 - - yield env.timeout(delay) - while True: - if isinstance(entities, PowerAware): - measurement = entities.measure_power() + def __init__(self, entities: Union[PowerAware, Collection[PowerAware], Callable[[], Collection[PowerAware]]], + name: Optional[str] = None, measurement_interval: Optional[float] = 1): + self.entities = entities + if name is None: + global _unnamed_power_meters_created + self.name = f"power_meter_{_unnamed_power_meters_created}" + _unnamed_power_meters_created += 1 else: - if isinstance(entities, Collection): - entities = entities - elif isinstance(entities, Callable): - entities = entities() + self.name = name + self.measurement_interval = measurement_interval + self.measurements = [] + + def run(self, env: simpy.Environment, delay: Optional[float] = 0): + """Starts the power meter process. + + Args: + env: Simpy environment (for timing the measurements) + delay: The delay after which the measurements shall be conducted. For some scenarios it makes sense to e.g. + include a tiny delay to make sure that all events at a previous time step were processed before the + measurement is conducted. + + Returns: + sim + """ + yield env.timeout(delay) + while True: + if isinstance(self.entities, PowerAware): + measurement = self.entities.measure_power() else: - raise ValueError(f"{name}: Unsupported type {type(entities)} for observable={entities}.") - measurement = PowerMeasurement.sum(entity.measure_power() for entity in entities) - callback(measurement) - logger.debug(f"{env.now}: {name}: {measurement}") - yield env.timeout(measurement_interval) + if isinstance(self.entities, Collection): + entities = self.entities + elif isinstance(self.entities, Callable): + entities = self.entities() + else: + raise ValueError(f"{self.name}: Unsupported type {type(self.entities)} for observable={self.entities}.") + measurement = PowerMeasurement.sum(entity.measure_power() for entity in entities) + self.measurements.append(measurement) + logger.debug(f"{env.now}: {self.name}: {measurement}") + yield env.timeout(self.measurement_interval)