From 7d29e6f63ba281331479dc61cab6a80b72e962d0 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 and cli tool 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 `--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, )