From 8a9325da8ab4d98e6f352025dd20bddbbeba1c3f Mon Sep 17 00:00:00 2001 From: Derek Maggio Date: Tue, 16 Apr 2024 10:06:52 -0700 Subject: [PATCH 1/5] chore: conditionally import time functions based on os --- .../robot_context_tracker.py | 26 +++++++++++++------ 1 file changed, 18 insertions(+), 8 deletions(-) diff --git a/performance-metrics/src/performance_metrics/robot_context_tracker.py b/performance-metrics/src/performance_metrics/robot_context_tracker.py index 188129046ff..2bd3744f65d 100644 --- a/performance-metrics/src/performance_metrics/robot_context_tracker.py +++ b/performance-metrics/src/performance_metrics/robot_context_tracker.py @@ -2,17 +2,27 @@ import csv from pathlib import Path +import platform + +from functools import wraps, partial +from time import perf_counter_ns import os +from typing import Callable, TypeVar, cast + +if platform.system() == "Linux": + from time import clock_gettime_ns, CLOCK_REALTIME + + time_ns = cast(Callable[[], int], partial(clock_gettime_ns, CLOCK_REALTIME)) +else: + # Compatability for Windows and MacOS + # This is to allow dev tests to run on Windows and MacOS + # The actual production usage is on the robot OS, which is Linux + from time import time_ns + -from functools import wraps -from time import perf_counter_ns, clock_gettime_ns, CLOCK_REALTIME -from typing import Callable, TypeVar from typing_extensions import ParamSpec from collections import deque -from performance_metrics.datashapes import ( - RawContextData, - RobotContextState, -) +from performance_metrics.datashapes import RawContextData, RobotContextState P = ParamSpec("P") R = TypeVar("R") @@ -43,7 +53,7 @@ def inner_decorator(func: Callable[P, R]) -> Callable[P, R]: @wraps(func) def wrapper(*args: P.args, **kwargs: P.kwargs) -> R: - function_start_time = clock_gettime_ns(CLOCK_REALTIME) + function_start_time = time_ns() duration_start_time = perf_counter_ns() try: result = func(*args, **kwargs) From ece7c7d4e8ca85e806ad712c733c8750de6ea19f Mon Sep 17 00:00:00 2001 From: Derek Maggio Date: Tue, 16 Apr 2024 10:16:29 -0700 Subject: [PATCH 2/5] test: testy tests --- .../robot_context_tracker.py | 25 +++++---- .../test_robot_context_tracker.py | 56 ++++++++++++++++++- 2 files changed, 69 insertions(+), 12 deletions(-) diff --git a/performance-metrics/src/performance_metrics/robot_context_tracker.py b/performance-metrics/src/performance_metrics/robot_context_tracker.py index 2bd3744f65d..03757f4ff86 100644 --- a/performance-metrics/src/performance_metrics/robot_context_tracker.py +++ b/performance-metrics/src/performance_metrics/robot_context_tracker.py @@ -9,16 +9,6 @@ import os from typing import Callable, TypeVar, cast -if platform.system() == "Linux": - from time import clock_gettime_ns, CLOCK_REALTIME - - time_ns = cast(Callable[[], int], partial(clock_gettime_ns, CLOCK_REALTIME)) -else: - # Compatability for Windows and MacOS - # This is to allow dev tests to run on Windows and MacOS - # The actual production usage is on the robot OS, which is Linux - from time import time_ns - from typing_extensions import ParamSpec from collections import deque @@ -27,6 +17,19 @@ P = ParamSpec("P") R = TypeVar("R") +def _get_timing_function() -> Callable[[], int]: + """Returns a timing function for the current platform.""" + time_function: Callable[[], int] + if platform.system() == "Linux": + from time import clock_gettime_ns, CLOCK_REALTIME + + time_function = cast(Callable[[], int], partial(clock_gettime_ns, CLOCK_REALTIME)) + else: + from time import time_ns + time_function = time_ns + + return time_function + class RobotContextTracker: """Tracks and stores robot context and execution duration for different operations.""" @@ -53,7 +56,7 @@ def inner_decorator(func: Callable[P, R]) -> Callable[P, R]: @wraps(func) def wrapper(*args: P.args, **kwargs: P.kwargs) -> R: - function_start_time = time_ns() + function_start_time = _get_timing_function()() duration_start_time = perf_counter_ns() try: result = func(*args, **kwargs) diff --git a/performance-metrics/tests/performance_metrics/test_robot_context_tracker.py b/performance-metrics/tests/performance_metrics/test_robot_context_tracker.py index d78d5054fe6..a6230983f6f 100644 --- a/performance-metrics/tests/performance_metrics/test_robot_context_tracker.py +++ b/performance-metrics/tests/performance_metrics/test_robot_context_tracker.py @@ -5,7 +5,8 @@ import pytest from performance_metrics.robot_context_tracker import RobotContextTracker from performance_metrics.datashapes import RobotContextState -from time import sleep +from time import sleep, time_ns +from unittest.mock import patch # Corrected times in seconds STARTING_TIME = 0.001 @@ -140,6 +141,24 @@ async def async_analyzing_operation() -> None: ), "State should be ANALYZING_PROTOCOL." +def test_sync_operation_timing_accuracy( + robot_context_tracker: RobotContextTracker, +) -> None: + """Tests the timing accuracy of a synchronous operation tracking.""" + + @robot_context_tracker.track(state=RobotContextState.RUNNING_PROTOCOL) + def running_operation() -> None: + sleep(RUNNING_TIME) + + running_operation() + + duration_data = robot_context_tracker._storage[0] + measured_duration = duration_data.duration_end - duration_data.duration_start + assert ( + abs(measured_duration - RUNNING_TIME * 1e9) < 1e7 + ), "Measured duration for sync operation should closely match the expected duration." + + @pytest.mark.asyncio async def test_async_operation_timing_accuracy( robot_context_tracker: RobotContextTracker, @@ -249,3 +268,38 @@ def analyzing_protocol() -> None: assert ( len(lines) == 4 ), "All stored data + header should be written to the file." + + +@patch("performance_metrics.robot_context_tracker._get_timing_function", return_value=time_ns) +def test_using_non_linux_time_functions(tmp_path: Path) -> None: + """Tests tracking operations using non-Linux time functions.""" + file_path = tmp_path / "test_file.csv" + robot_context_tracker = RobotContextTracker(file_path, should_track=True) + + @robot_context_tracker.track(state=RobotContextState.STARTING_UP) + def starting_robot() -> None: + sleep(STARTING_TIME) + + @robot_context_tracker.track(state=RobotContextState.CALIBRATING) + def calibrating_robot() -> None: + sleep(CALIBRATING_TIME) + + starting_robot() + calibrating_robot() + + storage = robot_context_tracker._storage + assert all( + data.func_start > 0 for data in storage + ), "All function start times should be greater than 0." + assert all( + data.duration_start > 0 for data in storage + ), "All duration start times should be greater than 0." + assert all( + data.duration_end > 0 for data in storage + ), "All duration end times should be greater than 0." + assert all( + data.duration_end > data.duration_start for data in storage + ), "Duration end times should be greater than duration start times." + assert len(storage) == 2, "Both operations should be tracked." + + From 4f8b81058ac6448f6a5ffc29f81f18bcb0a2560a Mon Sep 17 00:00:00 2001 From: Derek Maggio Date: Tue, 16 Apr 2024 10:21:21 -0700 Subject: [PATCH 3/5] chore: formatting --- .../src/performance_metrics/robot_context_tracker.py | 6 +++++- .../performance_metrics/test_robot_context_tracker.py | 7 ++++--- 2 files changed, 9 insertions(+), 4 deletions(-) diff --git a/performance-metrics/src/performance_metrics/robot_context_tracker.py b/performance-metrics/src/performance_metrics/robot_context_tracker.py index 03757f4ff86..6f29973b647 100644 --- a/performance-metrics/src/performance_metrics/robot_context_tracker.py +++ b/performance-metrics/src/performance_metrics/robot_context_tracker.py @@ -17,15 +17,19 @@ P = ParamSpec("P") R = TypeVar("R") + def _get_timing_function() -> Callable[[], int]: """Returns a timing function for the current platform.""" time_function: Callable[[], int] if platform.system() == "Linux": from time import clock_gettime_ns, CLOCK_REALTIME - time_function = cast(Callable[[], int], partial(clock_gettime_ns, CLOCK_REALTIME)) + time_function = cast( + Callable[[], int], partial(clock_gettime_ns, CLOCK_REALTIME) + ) else: from time import time_ns + time_function = time_ns return time_function diff --git a/performance-metrics/tests/performance_metrics/test_robot_context_tracker.py b/performance-metrics/tests/performance_metrics/test_robot_context_tracker.py index a6230983f6f..5345004eb44 100644 --- a/performance-metrics/tests/performance_metrics/test_robot_context_tracker.py +++ b/performance-metrics/tests/performance_metrics/test_robot_context_tracker.py @@ -270,7 +270,10 @@ def analyzing_protocol() -> None: ), "All stored data + header should be written to the file." -@patch("performance_metrics.robot_context_tracker._get_timing_function", return_value=time_ns) +@patch( + "performance_metrics.robot_context_tracker._get_timing_function", + return_value=time_ns, +) def test_using_non_linux_time_functions(tmp_path: Path) -> None: """Tests tracking operations using non-Linux time functions.""" file_path = tmp_path / "test_file.csv" @@ -301,5 +304,3 @@ def calibrating_robot() -> None: data.duration_end > data.duration_start for data in storage ), "Duration end times should be greater than duration start times." assert len(storage) == 2, "Both operations should be tracked." - - From 2ef8fbd45bf9ae234153f649af59b0bbd9abb318 Mon Sep 17 00:00:00 2001 From: Derek Maggio Date: Tue, 16 Apr 2024 11:03:52 -0700 Subject: [PATCH 4/5] fix: only call once --- .../src/performance_metrics/robot_context_tracker.py | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/performance-metrics/src/performance_metrics/robot_context_tracker.py b/performance-metrics/src/performance_metrics/robot_context_tracker.py index 6f29973b647..12ea565655f 100644 --- a/performance-metrics/src/performance_metrics/robot_context_tracker.py +++ b/performance-metrics/src/performance_metrics/robot_context_tracker.py @@ -34,6 +34,8 @@ def _get_timing_function() -> Callable[[], int]: return time_function +timing_function = _get_timing_function() + class RobotContextTracker: """Tracks and stores robot context and execution duration for different operations.""" @@ -60,7 +62,7 @@ def inner_decorator(func: Callable[P, R]) -> Callable[P, R]: @wraps(func) def wrapper(*args: P.args, **kwargs: P.kwargs) -> R: - function_start_time = _get_timing_function()() + function_start_time = timing_function() duration_start_time = perf_counter_ns() try: result = func(*args, **kwargs) From e18900eec9d6410d94c359938d4a663da1a71494 Mon Sep 17 00:00:00 2001 From: Derek Maggio Date: Tue, 16 Apr 2024 11:25:20 -0700 Subject: [PATCH 5/5] Formatting --- .../src/performance_metrics/robot_context_tracker.py | 1 + 1 file changed, 1 insertion(+) diff --git a/performance-metrics/src/performance_metrics/robot_context_tracker.py b/performance-metrics/src/performance_metrics/robot_context_tracker.py index 12ea565655f..606be71e649 100644 --- a/performance-metrics/src/performance_metrics/robot_context_tracker.py +++ b/performance-metrics/src/performance_metrics/robot_context_tracker.py @@ -34,6 +34,7 @@ def _get_timing_function() -> Callable[[], int]: return time_function + timing_function = _get_timing_function()