Skip to content

Commit e3092e7

Browse files
committed
Represent durations as timedeltas rather than as floats
In the component status tracking parts of the power distributor. Signed-off-by: Sahas Subramanian <[email protected]>
1 parent 8581904 commit e3092e7

File tree

6 files changed

+75
-77
lines changed

6 files changed

+75
-77
lines changed

src/frequenz/sdk/actor/power_distributing/_component_managers/_battery_manager.py

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -145,8 +145,8 @@ def __init__(
145145
self._component_pool_status_tracker = ComponentPoolStatusTracker(
146146
component_ids=set(self._battery_ids),
147147
component_status_sender=component_pool_status_sender,
148-
max_blocking_duration_sec=30.0,
149-
max_data_age_sec=10.0,
148+
max_blocking_duration=timedelta(seconds=30.0),
149+
max_data_age=timedelta(seconds=10.0),
150150
component_status_tracker_type=BatteryStatusTracker,
151151
)
152152

src/frequenz/sdk/actor/power_distributing/_component_pool_status_tracker.py

Lines changed: 14 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@
77
import asyncio
88
import logging
99
from collections import abc
10+
from datetime import timedelta
1011

1112
from frequenz.channels import Broadcast, Receiver, Sender
1213
from frequenz.channels.util import Merge
@@ -34,8 +35,8 @@ def __init__( # pylint: disable=too-many-arguments
3435
self,
3536
component_ids: abc.Set[int],
3637
component_status_sender: Sender[ComponentPoolStatus],
37-
max_data_age_sec: float,
38-
max_blocking_duration_sec: float,
38+
max_data_age: timedelta,
39+
max_blocking_duration: timedelta,
3940
component_status_tracker_type: type[ComponentStatusTracker],
4041
) -> None:
4142
"""Create ComponentPoolStatusTracker instance.
@@ -44,18 +45,17 @@ def __init__( # pylint: disable=too-many-arguments
4445
component_ids: set of component ids whose status is to be tracked.
4546
component_status_sender: The sender used for sending the status of the
4647
tracked components.
47-
max_data_age_sec: If a component stops sending data, then this is the
48-
maximum time for which its last message should be considered as
49-
valid. After that time, the component won't be used until it starts
50-
sending data.
51-
max_blocking_duration_sec: This value tell what should be the maximum
52-
timeout used for blocking failing component.
53-
component_status_tracker_type: component status tracker to use
54-
for tracking the status of the components.
48+
max_data_age: If a component stops sending data, then this is the maximum
49+
time for which its last message should be considered as valid. After
50+
that time, the component won't be used until it starts sending data.
51+
max_blocking_duration: This value tell what should be the maximum timeout
52+
used for blocking failing component.
53+
component_status_tracker_type: component status tracker to use for tracking
54+
the status of the components.
5555
"""
5656
self._component_ids = component_ids
57-
self._max_data_age_sec = max_data_age_sec
58-
self._max_blocking_duration_sec = max_blocking_duration_sec
57+
self._max_data_age = max_data_age
58+
self._max_blocking_duration = max_blocking_duration
5959
self._component_status_sender = component_status_sender
6060
self._component_status_tracker_type = component_status_tracker_type
6161

@@ -99,8 +99,8 @@ def _make_merged_status_receiver(
9999
)
100100
tracker = self._component_status_tracker_type(
101101
component_id=component_id,
102-
max_data_age_sec=self._max_data_age_sec,
103-
max_blocking_duration_sec=self._max_blocking_duration_sec,
102+
max_data_age=self._max_data_age,
103+
max_blocking_duration=self._max_blocking_duration,
104104
status_sender=channel.new_sender(),
105105
set_power_result_receiver=self._set_power_result_channel.new_receiver(),
106106
)

src/frequenz/sdk/actor/power_distributing/_component_status/_battery_status_tracker.py

Lines changed: 35 additions & 38 deletions
Original file line numberDiff line numberDiff line change
@@ -55,55 +55,53 @@ class _ComponentStreamStatus:
5555

5656
@dataclass
5757
class _BlockingStatus:
58-
min_duration_sec: float
59-
"""The minimum blocking duration (in seconds)."""
58+
min_duration: timedelta
59+
"""The minimum blocking duration."""
6060

61-
max_duration_sec: float
62-
"""The maximum blocking duration (in seconds)."""
61+
max_duration: timedelta
62+
"""The maximum blocking duration."""
6363

64-
last_blocking_duration_sec: float = 0.0
65-
"""Last blocking duration (in seconds)."""
64+
last_blocking_duration: timedelta = timedelta(seconds=0.0)
65+
"""Last blocking duration."""
6666

6767
blocked_until: datetime | None = None
6868
"""Until when the battery is blocked."""
6969

7070
def __post_init__(self) -> None:
71-
assert self.min_duration_sec <= self.max_duration_sec, (
72-
f"Minimum blocking duration ({self.min_duration_sec}) cannot be greater "
73-
f"than maximum blocking duration ({self.max_duration_sec})"
71+
assert self.min_duration <= self.max_duration, (
72+
f"Minimum blocking duration ({self.min_duration}) cannot be greater "
73+
f"than maximum blocking duration ({self.max_duration})"
7474
)
75-
self.last_blocking_duration_sec = self.min_duration_sec
75+
self.last_blocking_duration = self.min_duration
7676

77-
def block(self) -> float:
77+
def block(self) -> timedelta:
7878
"""Block battery.
7979
8080
Battery can be unblocked using `self.unblock()` method.
8181
8282
Returns:
83-
For how long (in seconds) the battery is blocked.
83+
The duration for which the battery is blocked.
8484
"""
8585
now = datetime.now(tz=timezone.utc)
8686

8787
# If is not blocked
8888
if self.blocked_until is None:
89-
self.last_blocking_duration_sec = self.min_duration_sec
90-
self.blocked_until = now + timedelta(
91-
seconds=self.last_blocking_duration_sec
92-
)
93-
return self.last_blocking_duration_sec
89+
self.last_blocking_duration = self.min_duration
90+
self.blocked_until = now + self.last_blocking_duration
91+
return self.last_blocking_duration
9492

9593
# If still blocked, then do nothing
9694
if self.blocked_until > now:
97-
return 0.0
95+
return timedelta(seconds=0.0)
9896

9997
# If previous blocking time expired, then blocked it once again.
10098
# Increase last blocking time, unless it reach the maximum.
101-
self.last_blocking_duration_sec = min(
102-
2 * self.last_blocking_duration_sec, self.max_duration_sec
99+
self.last_blocking_duration = min(
100+
2 * self.last_blocking_duration, self.max_duration
103101
)
104-
self.blocked_until = now + timedelta(seconds=self.last_blocking_duration_sec)
102+
self.blocked_until = now + self.last_blocking_duration
105103

106-
return self.last_blocking_duration_sec
104+
return self.last_blocking_duration
107105

108106
def unblock(self) -> None:
109107
"""Unblock battery.
@@ -166,20 +164,19 @@ class BatteryStatusTracker(ComponentStatusTracker):
166164
def __init__( # pylint: disable=too-many-arguments
167165
self,
168166
component_id: int,
169-
max_data_age_sec: float,
170-
max_blocking_duration_sec: float,
167+
max_data_age: timedelta,
168+
max_blocking_duration: timedelta,
171169
status_sender: Sender[ComponentStatus],
172170
set_power_result_receiver: Receiver[SetPowerResult],
173171
) -> None:
174172
"""Create class instance.
175173
176174
Args:
177175
component_id: Id of this battery
178-
max_data_age_sec: If component stopped sending data, then
179-
this is the maximum time when its last message should be considered as
180-
valid. After that time, component won't be used until it starts sending
181-
data.
182-
max_blocking_duration_sec: This value tell what should be the maximum
176+
max_data_age: If component stopped sending data, then this is the maximum
177+
time when its last message should be considered as valid. After that
178+
time, component won't be used until it starts sending data.
179+
max_blocking_duration: This value tell what should be the maximum
183180
timeout used for blocking failing component.
184181
status_sender: Channel to send status updates.
185182
set_power_result_receiver: Channel to receive results of the requests to the
@@ -188,12 +185,12 @@ def __init__( # pylint: disable=too-many-arguments
188185
Raises:
189186
RuntimeError: If battery has no adjacent inverter.
190187
"""
191-
self._max_data_age = max_data_age_sec
188+
self._max_data_age = max_data_age
192189
# First battery is considered as not working.
193190
# Change status after first messages are received.
194191
self._last_status: ComponentStatusEnum = ComponentStatusEnum.NOT_WORKING
195192
self._blocking_status: _BlockingStatus = _BlockingStatus(
196-
1.0, max_blocking_duration_sec
193+
timedelta(seconds=1.0), max_blocking_duration
197194
)
198195

199196
inverter_id = self._find_adjacent_inverter_id(component_id)
@@ -204,11 +201,11 @@ def __init__( # pylint: disable=too-many-arguments
204201

205202
self._battery: _ComponentStreamStatus = _ComponentStreamStatus(
206203
component_id,
207-
data_recv_timer=Timer.timeout(timedelta(seconds=max_data_age_sec)),
204+
data_recv_timer=Timer.timeout(max_data_age),
208205
)
209206
self._inverter: _ComponentStreamStatus = _ComponentStreamStatus(
210207
inverter_id,
211-
data_recv_timer=Timer.timeout(timedelta(seconds=max_data_age_sec)),
208+
data_recv_timer=Timer.timeout(max_data_age),
212209
)
213210

214211
# Select needs receivers that can be get in async way only.
@@ -259,9 +256,9 @@ def _handle_status_set_power_result(self, result: SetPowerResult) -> None:
259256
):
260257
duration = self._blocking_status.block()
261258

262-
if duration > 0:
259+
if duration > timedelta(seconds=0.0):
263260
_logger.warning(
264-
"battery %d failed last response. block it for %f sec",
261+
"battery %d failed last response. block it for %s",
265262
self.battery_id,
266263
duration,
267264
)
@@ -345,7 +342,7 @@ async def _run(
345342
if (
346343
datetime.now(tz=timezone.utc)
347344
- self._battery.last_msg_timestamp
348-
) < timedelta(seconds=self._max_data_age):
345+
) < self._max_data_age:
349346
# This means that we have received data from the battery
350347
# since the timer triggered, but the timer event arrived
351348
# late, so we can ignore it.
@@ -356,7 +353,7 @@ async def _run(
356353
if (
357354
datetime.now(tz=timezone.utc)
358355
- self._inverter.last_msg_timestamp
359-
) < timedelta(seconds=self._max_data_age):
356+
) < self._max_data_age:
360357
# This means that we have received data from the inverter
361358
# since the timer triggered, but the timer event arrived
362359
# late, so we can ignore it.
@@ -505,7 +502,7 @@ def _is_timestamp_outdated(self, timestamp: datetime) -> bool:
505502
_True if timestamp is to old, False otherwise
506503
"""
507504
now = datetime.now(tz=timezone.utc)
508-
diff = (now - timestamp).total_seconds()
505+
diff = now - timestamp
509506
return diff > self._max_data_age
510507

511508
def _is_message_reliable(self, message: ComponentData) -> bool:

src/frequenz/sdk/actor/power_distributing/_component_status/_component_status.py

Lines changed: 7 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@
88
from abc import ABC, abstractmethod
99
from collections import abc
1010
from dataclasses import dataclass
11+
from datetime import timedelta
1112

1213
from frequenz.channels import Receiver, Sender
1314

@@ -83,20 +84,19 @@ class ComponentStatusTracker(ABC):
8384
def __init__( # pylint: disable=too-many-arguments
8485
self,
8586
component_id: int,
86-
max_data_age_sec: float,
87-
max_blocking_duration_sec: float,
87+
max_data_age: timedelta,
88+
max_blocking_duration: timedelta,
8889
status_sender: Sender[ComponentStatus],
8990
set_power_result_receiver: Receiver[SetPowerResult],
9091
) -> None:
9192
"""Create class instance.
9293
9394
Args:
9495
component_id: Id of this component
95-
max_data_age_sec: If component stopped sending data, then
96-
this is the maximum time when its last message should be considered as
97-
valid. After that time, component won't be used until it starts sending
98-
data.
99-
max_blocking_duration_sec: This value tell what should be the maximum
96+
max_data_age: If component stopped sending data, then this is the maximum
97+
time when its last message should be considered as valid. After that
98+
time, component won't be used until it starts sending data.
99+
max_blocking_duration: This value tell what should be the maximum
100100
timeout used for blocking failing component.
101101
status_sender: Channel to send status updates.
102102
set_power_result_receiver: Channel to receive results of the requests to the

tests/actor/test_battery_pool_status.py

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@
33
"""Tests for BatteryPoolStatus."""
44

55
import asyncio
6+
from datetime import timedelta
67

78
from frequenz.channels import Broadcast
89
from pytest_mock import MockerFixture
@@ -47,8 +48,8 @@ async def test_batteries_status(self, mocker: MockerFixture) -> None:
4748
batteries_status = ComponentPoolStatusTracker(
4849
component_ids=batteries,
4950
component_status_sender=battery_status_channel.new_sender(),
50-
max_data_age_sec=5,
51-
max_blocking_duration_sec=30,
51+
max_data_age=timedelta(seconds=5),
52+
max_blocking_duration=timedelta(seconds=30),
5253
component_status_tracker_type=BatteryStatusTracker,
5354
)
5455
await asyncio.sleep(0.1)

tests/actor/test_battery_status.py

Lines changed: 14 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -188,8 +188,8 @@ async def test_sync_update_status_with_messages(
188188

189189
async with mock_microgrid, battery_status_tracker(
190190
BATTERY_ID,
191-
max_data_age_sec=5,
192-
max_blocking_duration_sec=30,
191+
max_data_age=timedelta(seconds=5),
192+
max_blocking_duration=timedelta(seconds=30),
193193
status_sender=status_channel.new_sender(),
194194
set_power_result_receiver=set_power_result_channel.new_receiver(),
195195
) as tracker:
@@ -358,8 +358,8 @@ async def test_sync_blocking_feature(self, mocker: MockerFixture) -> None:
358358
# increase max_data_age_sec for blocking tests.
359359
# Otherwise it will block blocking.
360360
BATTERY_ID,
361-
max_data_age_sec=500,
362-
max_blocking_duration_sec=30,
361+
max_data_age=timedelta(seconds=500),
362+
max_blocking_duration=timedelta(seconds=30),
363363
status_sender=status_channel.new_sender(),
364364
set_power_result_receiver=set_power_result_channel.new_receiver(),
365365
) as tracker:
@@ -494,8 +494,8 @@ async def test_sync_blocking_interrupted_with_with_max_data(
494494

495495
async with mock_microgrid, battery_status_tracker(
496496
BATTERY_ID,
497-
max_data_age_sec=5,
498-
max_blocking_duration_sec=30,
497+
max_data_age=timedelta(seconds=5),
498+
max_blocking_duration=timedelta(seconds=30),
499499
status_sender=status_channel.new_sender(),
500500
set_power_result_receiver=set_power_result_channel.new_receiver(),
501501
) as tracker:
@@ -542,8 +542,8 @@ async def test_sync_blocking_interrupted_with_invalid_message(
542542

543543
async with mock_microgrid, battery_status_tracker(
544544
BATTERY_ID,
545-
max_data_age_sec=5,
546-
max_blocking_duration_sec=30,
545+
max_data_age=timedelta(seconds=5),
546+
max_blocking_duration=timedelta(seconds=30),
547547
status_sender=status_channel.new_sender(),
548548
set_power_result_receiver=set_power_result_channel.new_receiver(),
549549
) as tracker:
@@ -603,8 +603,8 @@ async def test_timers(
603603

604604
async with mock_microgrid, battery_status_tracker(
605605
BATTERY_ID,
606-
max_data_age_sec=5,
607-
max_blocking_duration_sec=30,
606+
max_data_age=timedelta(seconds=5),
607+
max_blocking_duration=timedelta(seconds=30),
608608
status_sender=status_channel.new_sender(),
609609
set_power_result_receiver=set_power_result_channel.new_receiver(),
610610
) as tracker:
@@ -669,8 +669,8 @@ async def test_async_battery_status(self, mocker: MockerFixture) -> None:
669669

670670
async with mock_microgrid, battery_status_tracker(
671671
BATTERY_ID,
672-
max_data_age_sec=5,
673-
max_blocking_duration_sec=30,
672+
max_data_age=timedelta(seconds=5),
673+
max_blocking_duration=timedelta(seconds=30),
674674
status_sender=status_channel.new_sender(),
675675
set_power_result_receiver=set_power_result_channel.new_receiver(),
676676
):
@@ -753,8 +753,8 @@ async def setup_tracker(
753753

754754
async with mock_microgrid, battery_status_tracker(
755755
BATTERY_ID,
756-
max_data_age_sec=0.1,
757-
max_blocking_duration_sec=1,
756+
max_data_age=timedelta(seconds=0.1),
757+
max_blocking_duration=timedelta(seconds=1),
758758
status_sender=status_channel.new_sender(),
759759
set_power_result_receiver=set_power_result_channel.new_receiver(),
760760
):

0 commit comments

Comments
 (0)