From 811a74055ceb848f9b5da6aa0cae7c0a427351fc Mon Sep 17 00:00:00 2001 From: cwasicki <126617870+cwasicki@users.noreply.github.com> Date: Fri, 13 Sep 2024 19:06:32 +0200 Subject: [PATCH] Add support for bounds in client Bounds can now be requested via the client and are provided through the flat iterator. They can be identified via their category `metric_bound[i]_{upper,lower}`. Each individual bound is provided as its own sample. Support for states and bound is also added to the CLI tool via the `--states` and `--bounds` flag, Signed-off-by: cwasicki <126617870+cwasicki@users.noreply.github.com> --- RELEASE_NOTES.md | 5 +++- src/frequenz/client/reporting/__main__.py | 9 +++++++ src/frequenz/client/reporting/_client.py | 31 +++++++++++++++++++++++ 3 files changed, 44 insertions(+), 1 deletion(-) diff --git a/RELEASE_NOTES.md b/RELEASE_NOTES.md index e8ab747..2fcd033 100644 --- a/RELEASE_NOTES.md +++ b/RELEASE_NOTES.md @@ -13,8 +13,11 @@ * States can now be requested via the client and are provided through the flat iterator. They can be identified via their category `state`, `warning` and `error`, respectively. Each individual state is provided as its own sample. +* Bounds can now be requested via the client and are provided through the flat iterator. + They can be identified via their category `metric_bound[i]_{upper,lower}`. + Each individual bound is provided as its own sample. -* Support for states is also added to the CLI tool via the `--states` flag. +* Support for states and bound is also added to the CLI tool via the `--states` and `--bounds` flag, respectively. ## Bug Fixes diff --git a/src/frequenz/client/reporting/__main__.py b/src/frequenz/client/reporting/__main__.py index fd7c427..d923bd0 100644 --- a/src/frequenz/client/reporting/__main__.py +++ b/src/frequenz/client/reporting/__main__.py @@ -39,6 +39,11 @@ def main() -> None: action="store_true", help="Include states in the output", ) + parser.add_argument( + "--bounds", + action="store_true", + help="Include bounds in the output", + ) parser.add_argument( "--start", type=datetime.fromisoformat, @@ -72,6 +77,7 @@ def main() -> None: args.end, args.resolution, states=args.states, + bounds=args.bounds, service_address=args.url, key=args.key, fmt=args.format, @@ -88,6 +94,7 @@ async def run( end_dt: datetime, resolution: int, states: bool, + bounds: bool, service_address: str, key: str, fmt: str, @@ -102,6 +109,7 @@ async def run( end_dt: end datetime resolution: resampling resolution in sec states: include states in the output + bounds: include bounds in the output service_address: service address key: API key fmt: output format @@ -129,6 +137,7 @@ def data_iter() -> AsyncIterator[MetricSample]: end_dt=end_dt, resolution=resolution, include_states=states, + include_bounds=bounds, ) if fmt == "iter": diff --git a/src/frequenz/client/reporting/_client.py b/src/frequenz/client/reporting/_client.py index 85bf178..3bb3214 100644 --- a/src/frequenz/client/reporting/_client.py +++ b/src/frequenz/client/reporting/_client.py @@ -98,6 +98,23 @@ def __iter__(self) -> Iterator[MetricSample]: metric=met, value=value, ) + for i, bound in enumerate(msample.bounds): + if bound.lower: + yield MetricSample( + timestamp=ts, + microgrid_id=mid, + component_id=cid, + metric=f"{met}_bound_{i}_lower", + value=bound.lower, + ) + if bound.upper: + yield MetricSample( + timestamp=ts, + microgrid_id=mid, + component_id=cid, + metric=f"{met}_bound_{i}_upper", + value=bound.upper, + ) for state in cdata.states: ts = state.sampled_at.ToDatetime() for name, category in { @@ -145,6 +162,7 @@ async def list_single_component_data( end_dt: datetime, resolution: int | None, include_states: bool = False, + include_bounds: bool = False, ) -> AsyncIterator[MetricSample]: """Iterate over the data for a single metric. @@ -156,6 +174,7 @@ async def list_single_component_data( end_dt: The end date and time. resolution: The resampling resolution for the data, represented in seconds. include_states: Whether to include the state data. + include_bounds: Whether to include the bound data. Yields: A named tuple with the following fields: @@ -169,6 +188,7 @@ async def list_single_component_data( end_dt=end_dt, resolution=resolution, include_states=include_states, + include_bounds=include_bounds, ): for entry in batch: yield entry @@ -183,6 +203,7 @@ async def list_microgrid_components_data( end_dt: datetime, resolution: int | None, include_states: bool = False, + include_bounds: bool = False, ) -> AsyncIterator[MetricSample]: """Iterate over the data for multiple microgrids and components. @@ -194,6 +215,7 @@ async def list_microgrid_components_data( end_dt: The end date and time. resolution: The resampling resolution for the data, represented in seconds. include_states: Whether to include the state data. + include_bounds: Whether to include the bound data. Yields: A named tuple with the following fields: @@ -210,6 +232,7 @@ async def list_microgrid_components_data( end_dt=end_dt, resolution=resolution, include_states=include_states, + include_bounds=include_bounds, ): for entry in batch: yield entry @@ -225,6 +248,7 @@ async def _list_microgrid_components_data_batch( end_dt: datetime, resolution: int | None, include_states: bool = False, + include_bounds: bool = False, ) -> AsyncIterator[ComponentsDataBatch]: """Iterate over the component data batches in the stream. @@ -238,6 +262,7 @@ async def _list_microgrid_components_data_batch( end_dt: The end date and time. resolution: The resampling resolution for the data, represented in seconds. include_states: Whether to include the state data. + include_bounds: Whether to include the bound data. Yields: A ComponentsDataBatch object of microgrid components data. @@ -262,7 +287,13 @@ def dt2ts(dt: datetime) -> PBTimestamp: if include_states else PBIncludeOptions.FilterOption.FILTER_OPTION_EXCLUDE ) + incl_bounds = ( + PBIncludeOptions.FilterOption.FILTER_OPTION_INCLUDE + if include_bounds + else PBIncludeOptions.FilterOption.FILTER_OPTION_EXCLUDE + ) include_options = PBIncludeOptions( + bounds=incl_bounds, states=incl_states, )