Skip to content

Commit

Permalink
feat: response time timeseries (#322)
Browse files Browse the repository at this point in the history
* diff_node_names

Signed-off-by: yu-taro- <[email protected]>

* 20230130

Signed-off-by: yu-taro- <[email protected]>

* diff_node_names 20230205

* re

Signed-off-by: yu-taro- <[email protected]>

* class Diff

Signed-off-by: yu-taro- <[email protected]>

* 20230205

Signed-off-by: yu-taro- <[email protected]>

* refactor(arcitecture), test(test_arcitecture) / Naming conventions were changed and mock tests were added and modified because of the points raised

Signed-off-by: yu-taro- <[email protected]>

* a

Signed-off-by: yu-taro- <[email protected]>

* conflict fix2

Signed-off-by: yu-taro- <[email protected]>

* feat: make response time time series plot func

Signed-off-by: taro-yu <[email protected]>

* fix: conflict

Signed-off-by: taro-yu <[email protected]>

* fix: changed to use new record api

Signed-off-by: taro-yu <[email protected]>

* fix: changed to use new record api

Signed-off-by: taro-yu <[email protected]>

* fix: pytest errors

Signed-off-by: taro-yu <[email protected]>

* docs: add some description

Signed-off-by: taro-yu <[email protected]>

* docs: add some description

Signed-off-by: taro-yu <[email protected]>

* fix: argument error

Signed-off-by: taro-yu <[email protected]>

* feat: add hover info

Signed-off-by: taro-yu <[email protected]>

* docs: fix a comment

Signed-off-by: taro-yu <[email protected]>

* docs: added CARET_doc and fixed spell-ceheck-errors

Signed-off-by: taro-yu <[email protected]>

* docs: fixed spell-check-error

Signed-off-by: taro-yu <[email protected]>

* fix: mypy errors

Signed-off-by: taro-yu <[email protected]>

* fix: mypy errors

Signed-off-by: taro-yu <[email protected]>

* fix: mypy errors

Signed-off-by: taro-yu <[email protected]>

* fix: mypy errors and add some docs

Signed-off-by: taro-yu <[email protected]>

* fix: pytest errors

Signed-off-by: taro-yu <[email protected]>

* fix: mypy errors

Signed-off-by: taro-yu <[email protected]>

* docs remove some docs

Signed-off-by: taro-yu <[email protected]>

* feat: change hover info

Signed-off-by: taro-yu <[email protected]>

---------

Signed-off-by: yu-taro- <[email protected]>
Signed-off-by: taro-yu <[email protected]>
Co-authored-by: taro-yu <[email protected]>
  • Loading branch information
taro-yu and taro-yu authored Sep 13, 2023
1 parent b9ad974 commit 7efeaba
Show file tree
Hide file tree
Showing 9 changed files with 210 additions and 28 deletions.
8 changes: 6 additions & 2 deletions src/caret_analyze/plot/metrics_base.py
Original file line number Diff line number Diff line change
Expand Up @@ -20,9 +20,9 @@
import pandas as pd

from ..record import ColumnValue, Range, RecordFactory, RecordsFactory, RecordsInterface
from ..runtime import CallbackBase, Communication, Publisher, Subscription
from ..runtime import CallbackBase, Communication, Path, Publisher, Subscription

TimeSeriesTypes = CallbackBase | Communication | (Publisher | Subscription)
TimeSeriesTypes = CallbackBase | Communication | (Publisher | Subscription) | Path


class MetricsBase(metaclass=ABCMeta):
Expand Down Expand Up @@ -65,6 +65,8 @@ def _convert_timeseries_records_to_sim_time(
break
provider = converter_cb._provider
converter = provider.get_sim_time_converter(frame_min, frame_max)
elif isinstance(self._target_objects[0], Path):
pass
else:
provider = self._target_objects[0]._provider
converter = provider.get_sim_time_converter(frame_min, frame_max)
Expand Down Expand Up @@ -122,5 +124,7 @@ def _add_top_level_column(
f'{target_object.subscribe_node_name}')
elif isinstance(target_object, (Publisher, Subscription)):
column_name = target_object.topic_name
elif isinstance(target_object, Path):
column_name = f'{target_object.column_names[0]} to {target_object.column_names[-1]}'

return pd.concat([target_df], keys=[column_name], axis=1)
29 changes: 28 additions & 1 deletion src/caret_analyze/plot/plot_facade.py
Original file line number Diff line number Diff line change
Expand Up @@ -29,7 +29,7 @@

logger = getLogger(__name__)

TimeSeriesTypes = CallbackBase | Communication | Publisher | Subscription
TimeSeriesTypes = CallbackBase | Communication | Publisher | Subscription | Path
CallbackSchedTypes = (Application | Executor | Path |
Node | CallbackGroup | Collection[CallbackGroup])

Expand Down Expand Up @@ -174,6 +174,33 @@ def create_latency_timeseries_plot(
)
return plot

def create_response_time_timeseries_plot(
*target_objects: Path,
case: str = 'best'
) -> PlotBase:
"""
Get response time timeseries plot instance.
Parameters
----------
target_objects : Collection[Path]
Instances that are the sources of the plotting.
This also accepts multiple inputs by unpacking.
case: str, optional
Response time calculation method, best by default.
supported case: [best/worst/all].
Returns
-------
PlotBase
"""
visualize_lib = VisualizeLibFactory.create_instance()
plot = TimeSeriesPlotFactory.create_instance(
parse_collection_or_unpack(target_objects), 'response_time', visualize_lib, case
)
return plot

@staticmethod
def create_response_time_histogram_plot(
*paths: Path,
Expand Down
115 changes: 115 additions & 0 deletions src/caret_analyze/plot/timeseries/response_time_timeseries.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,115 @@
# Copyright 2021 Research Institute of Systems Planning, Inc.
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.

from __future__ import annotations

from collections.abc import Sequence

import pandas as pd

from ..metrics_base import MetricsBase
from ...record import RecordsInterface, ResponseTime
from ...runtime import Path


class ResponseTimeTimeSeries(MetricsBase):
"""Class that provides latency timeseries data."""

def __init__(
self,
target_objects: Sequence[Path],
case: str
) -> None:
super().__init__(target_objects)
self._case = case

def to_dataframe(self, xaxis_type: str = 'system_time') -> pd.DataFrame:
"""
Get Response time timeseries data for each object in pandas DataFrame format.
Parameters
----------
xaxis_type : str
Type of time for timestamp.
"system_time", "index", or "sim_time" can be specified.
The default is "system_time".
Returns
-------
pd.DataFrame
Multi-column Response time DataFrame.
Notes
-----
xaxis_type "system_time" and "index" return the same DataFrame.
"""
timeseries_records_list = self.to_timeseries_records_list(xaxis_type)
all_df = pd.DataFrame()
for to, response_records in zip(self._target_objects, timeseries_records_list):
response_df = response_records.to_dataframe()
response_df[response_df.columns[-1]] *= 10**(-6)
response_df.rename(
columns={
response_df.columns[0]: 'path_start_timestamp [ns]',
response_df.columns[1]: 'response_time [ms]',
},
inplace=True
)
# TODO: Multi-column DataFrame are difficult for users to handle,
# so it should be a single-column DataFrame.
response_df = self._add_top_level_column(response_df, to)
all_df = pd.concat([all_df, response_df], axis=1)

return all_df.sort_index(level=0, axis=1, sort_remaining=False)

def to_timeseries_records_list(
self,
xaxis_type: str = 'system_time'
) -> list[RecordsInterface]:
"""
Get Response time records list of all target objects.
Parameters
----------
xaxis_type : str
Type of time for timestamp.
"system_time", "index", or "sim_time" can be specified.
The default is "system_time".
Returns
-------
list[RecordsInterface]
Response time records list of all target objects.
"""
timeseries_records_list: list[RecordsInterface] = [
_.to_records() for _ in self._target_objects
]

if xaxis_type == 'sim_time':
timeseries_records_list = \
self._convert_timeseries_records_to_sim_time(timeseries_records_list)

response_timeseries_list: list[RecordsInterface] = []
for records in timeseries_records_list:
response = ResponseTime(records)
if self._case == 'best':
response_timeseries_list.append(response.to_best_case_records())
elif self._case == 'worst':
response_timeseries_list.append(response.to_worst_case_records())
elif self._case == 'all':
response_timeseries_list.append(response.to_all_records())

return response_timeseries_list
11 changes: 7 additions & 4 deletions src/caret_analyze/plot/timeseries/timeseries_plot.py
Original file line number Diff line number Diff line change
Expand Up @@ -22,9 +22,9 @@
from ..plot_base import PlotBase
from ..visualize_lib import VisualizeLibInterface
from ...exceptions import UnsupportedTypeError
from ...runtime import CallbackBase, Communication, Publisher, Subscription
from ...runtime import CallbackBase, Communication, Path, Publisher, Subscription

TimeSeriesTypes = CallbackBase | Communication | (Publisher | Subscription)
TimeSeriesTypes = CallbackBase | Communication | (Publisher | Subscription) | Path


class TimeSeriesPlot(PlotBase):
Expand All @@ -33,10 +33,12 @@ class TimeSeriesPlot(PlotBase):
def __init__(
self,
metrics: MetricsBase,
visualize_lib: VisualizeLibInterface
visualize_lib: VisualizeLibInterface,
case: str = 'best' # case is only used for response time timeseries.
) -> None:
self._metrics = metrics
self._visualize_lib = visualize_lib
self._case = case

def to_dataframe(self, xaxis_type: str = 'system_time') -> pd.DataFrame:
"""
Expand Down Expand Up @@ -104,7 +106,8 @@ def figure(
self._metrics,
xaxis_type,
ywheel_zoom,
full_legends
full_legends,
self._case
)

def _validate_xaxis_type(self, xaxis_type: str) -> None:
Expand Down
28 changes: 19 additions & 9 deletions src/caret_analyze/plot/timeseries/timeseries_plot_factory.py
Original file line number Diff line number Diff line change
Expand Up @@ -19,14 +19,15 @@
from .frequency_timeseries import FrequencyTimeSeries
from .latency_timeseries import LatencyTimeSeries
from .period_timeseries import PeriodTimeSeries
from .response_time_timeseries import ResponseTimeTimeSeries
from .timeseries_plot import TimeSeriesPlot
from ..metrics_base import MetricsBase
from ..visualize_lib import VisualizeLibInterface
from ...common import type_check_decorator
from ...exceptions import UnsupportedTypeError
from ...runtime import CallbackBase, Communication, Publisher, Subscription
from ...runtime import CallbackBase, Communication, Path, Publisher, Subscription

TimeSeriesPlotTypes = CallbackBase | Communication | (Publisher | Subscription)
TimeSeriesPlotTypes = CallbackBase | Communication | (Publisher | Subscription) | Path


class TimeSeriesPlotFactory:
Expand All @@ -37,20 +38,23 @@ class TimeSeriesPlotFactory:
def create_instance(
target_objects: Sequence[TimeSeriesPlotTypes],
metrics: str,
visualize_lib: VisualizeLibInterface
visualize_lib: VisualizeLibInterface,
case: str = 'best' # case is only used for response time timeseries.
) -> TimeSeriesPlot:
"""
Create an instance of TimeSeriesPlot.
Parameters
----------
target_objects : Sequence[TimeSeriesPlotTypes]
TimeSeriesPlotTypes = CallbackBase | Communication | (Publisher | Subscription)
TimeSeriesPlotTypes = CallbackBase | Communication | (Publisher | Subscription) | Path
metrics : str
Metrics for timeseries data.
supported metrics: [frequency/latency/period]
supported metrics: [frequency/latency/period/response_time]
visualize_lib : VisualizeLibInterface
Instance of VisualizeLibInterface used for visualization.
case : str
Parameter specifying best, worst or all. Use to create Response time timeseries graph.
Returns
-------
Expand All @@ -59,22 +63,28 @@ def create_instance(
Raises
------
UnsupportedTypeError
Argument metrics is not "frequency", "latency", or "period".
Argument metrics is not "frequency", "latency", "period", or "response_time".
"""
metrics_: MetricsBase
PlotTypes = (CallbackBase, Communication, Publisher, Subscription)
if metrics == 'frequency':
metrics_ = FrequencyTimeSeries(list(target_objects))
metrics_ = FrequencyTimeSeries([_ for _ in target_objects if isinstance(_, PlotTypes)])
return TimeSeriesPlot(metrics_, visualize_lib)
elif metrics == 'latency':
# Ignore the mypy type check because type_check_decorator is applied.
metrics_ = LatencyTimeSeries(list(target_objects)) # type: ignore
return TimeSeriesPlot(metrics_, visualize_lib)
elif metrics == 'period':
metrics_ = PeriodTimeSeries(list(target_objects))
metrics_ = PeriodTimeSeries([_ for _ in target_objects if isinstance(_, PlotTypes)])
return TimeSeriesPlot(metrics_, visualize_lib)
elif metrics == 'response_time':
metrics_ = ResponseTimeTimeSeries(
[_ for _ in target_objects if isinstance(_, Path)], case
)
return TimeSeriesPlot(metrics_, visualize_lib, case)
else:
raise UnsupportedTypeError(
'Unsupported metrics specified. '
'Supported metrics: [frequency/latency/period]'
'Supported metrics: [frequency/latency/period/response_time]'
)
10 changes: 7 additions & 3 deletions src/caret_analyze/plot/visualize_lib/bokeh/bokeh.py
Original file line number Diff line number Diff line change
Expand Up @@ -28,7 +28,7 @@
from ...metrics_base import MetricsBase
from ....runtime import CallbackBase, CallbackGroup, Communication, Path, Publisher, Subscription

TimeSeriesTypes = CallbackBase | Communication | (Publisher | Subscription)
TimeSeriesTypes = CallbackBase | Communication | (Publisher | Subscription) | Path

logger = getLogger(__name__)

Expand Down Expand Up @@ -131,7 +131,8 @@ def timeseries(
metrics: MetricsBase,
xaxis_type: str,
ywheel_zoom: bool,
full_legends: bool
full_legends: bool,
case: str
) -> Figure:
"""
Get a timeseries figure.
Expand All @@ -150,12 +151,15 @@ def timeseries(
full_legends : bool
If True, all legends are drawn
even if the number of legends exceeds the threshold.
case : str
Parameter specifying best, worst or all. Use to create Response time timeseries graph.
Returns
-------
bokeh.plotting.Figure
Figure of timeseries.
"""
timeseries = BokehTimeSeries(metrics, xaxis_type, ywheel_zoom, full_legends)
timeseries = BokehTimeSeries(metrics, xaxis_type, ywheel_zoom, full_legends, case)
return timeseries.create_figure()
Loading

0 comments on commit 7efeaba

Please sign in to comment.