Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: configure performance-metrics to work on robot #15316

Merged
merged 21 commits into from
Jun 25, 2024
Merged
Show file tree
Hide file tree
Changes from 18 commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -137,6 +137,7 @@ update-server/LICENSE
shared-data/python/LICENSE
shared-data/LICENSE
robot-server/LICENSE
performance-metrics/LICENSE

# typescript incremental files
*.tsbuildinfo
Expand Down
62 changes: 49 additions & 13 deletions api/src/opentrons/util/performance_helpers.py
Original file line number Diff line number Diff line change
Expand Up @@ -27,7 +27,7 @@
)


class StubbedTracker:
class _StubbedTracker:
"""A stubbed tracker that does nothing."""

def __init__(self, storage_location: Path, should_track: bool) -> None:
Expand Down Expand Up @@ -60,10 +60,10 @@ def store(self) -> None:
pass


# Ensure that StubbedTracker implements SupportsTracking
# Ensure that _StubbedTracker implements SupportsTracking
# but do not create a runtime dependency on performance_metrics
if typing.TYPE_CHECKING:
_: typing.Type["SupportsTracking"] = StubbedTracker
_: typing.Type["SupportsTracking"] = _StubbedTracker


def _handle_package_import() -> typing.Type["SupportsTracking"]:
Expand All @@ -76,13 +76,12 @@ def _handle_package_import() -> typing.Type["SupportsTracking"]:

return RobotContextTracker
except ImportError:
return StubbedTracker
return _StubbedTracker


package_to_use = _handle_package_import()
_package_to_use = _handle_package_import()
_robot_context_tracker: typing.Optional["SupportsTracking"] = None


# TODO: derek maggio (06-03-2024): investigate if _should_track should be
# reevaluated each time _get_robot_context_tracker is called. I think this
# might get stuck in a state where after the first call, _should_track is
Expand All @@ -94,19 +93,30 @@ def _get_robot_context_tracker() -> "SupportsTracking":
"""Singleton for the robot context tracker."""
global _robot_context_tracker
if _robot_context_tracker is None:
_robot_context_tracker = package_to_use(
_robot_context_tracker = _package_to_use(
get_performance_metrics_data_dir(), _should_track
)
return _robot_context_tracker


def track_analysis(
func: _UnderlyingFunction[_UnderlyingFunctionParameters, _UnderlyingFunctionReturn]
) -> _UnderlyingFunction[_UnderlyingFunctionParameters, _UnderlyingFunctionReturn]:
"""Track the analysis of a protocol and store each run."""
# TODO: derek maggio (06-03-2024): generalize creating wrapper functions for tracking different states
def _track_a_function(
state_name: "RobotContextState",
func: _UnderlyingFunction[_UnderlyingFunctionParameters, _UnderlyingFunctionReturn],
) -> typing.Callable[_UnderlyingFunctionParameters, _UnderlyingFunctionReturn]:
"""Track a function.

This function is a decorator that will track the given state for the
decorated function.

Args:
state_name: The state to annotate the tracked function with.
func: The function to decorate.

Returns:
The decorated function.
"""
tracker: SupportsTracking = _get_robot_context_tracker()
wrapped = tracker.track(state="ANALYZING_PROTOCOL")(func)
wrapped = tracker.track(state=state_name)(func)

@functools.wraps(func)
def wrapper(
Expand All @@ -116,6 +126,32 @@ def wrapper(
try:
return wrapped(*args, **kwargs)
finally:
# TODO: derek maggio (06-18-2024): After investigation, it appears on startup
# that the first call to tracker.store() will not actually store the data.
# The second call stores both rows of data.

tracker.store()

return wrapper


class TrackingFunctions:
"""A class for tracking functions."""

@staticmethod
def track_analysis(
func: _UnderlyingFunction[
_UnderlyingFunctionParameters, _UnderlyingFunctionReturn
]
) -> typing.Callable[_UnderlyingFunctionParameters, _UnderlyingFunctionReturn]:
"""Track a function that runs an analysis."""
return _track_a_function("ANALYZING_PROTOCOL", func)

@staticmethod
def track_getting_cached_protocol_analysis(
func: _UnderlyingFunction[
_UnderlyingFunctionParameters, _UnderlyingFunctionReturn
]
) -> typing.Callable[_UnderlyingFunctionParameters, _UnderlyingFunctionReturn]:
"""Track a function that gets cached analysis."""
return _track_a_function("GETTING_CACHED_ANALYSIS", func)
6 changes: 3 additions & 3 deletions api/tests/opentrons/util/test_performance_helpers.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,14 +2,14 @@

from pathlib import Path
from opentrons.util.performance_helpers import (
StubbedTracker,
_StubbedTracker,
_get_robot_context_tracker,
)


def test_return_function_unchanged() -> None:
"""Test that the function is returned unchanged when using StubbedTracker."""
tracker = StubbedTracker(Path("/path/to/storage"), True)
"""Test that the function is returned unchanged when using _StubbedTracker."""
tracker = _StubbedTracker(Path("/path/to/storage"), True)

def func_to_track() -> None:
pass
Expand Down
1 change: 0 additions & 1 deletion performance-metrics/.gitignore

This file was deleted.

87 changes: 85 additions & 2 deletions performance-metrics/Makefile
Original file line number Diff line number Diff line change
@@ -1,4 +1,38 @@
include ../scripts/python.mk
include ../scripts/push.mk

ot_project := $(OPENTRONS_PROJECT)
project_rs_default = $(if $(ot_project),$(ot_project),robot-stack)
project_ir_default = $(if $(ot_project),$(ot_project),ot3)

SHX := npx shx

# Host key location for robot
ssh_key ?= $(default_ssh_key)
# Other SSH args for robot
ssh_opts ?= $(default_ssh_opts)
# Helper to safely bundle ssh options
ssh_helper = $(if $(ssh_key),-i $(ssh_key)) $(ssh_opts)

# Defined separately than the clean target so the wheel file doesn’t have to
# depend on a PHONY target

# Find the version of the wheel from git using a helper script. We
# use python here so we can use the same version normalization that will be
# used to create the wheel.
wheel_file = dist/$(call python_get_wheelname,performance-metrics,$(project_rs_default),performance_metrics,$(BUILD_NUMBER))

# Find the version of the sdist file from git using a helper script.
sdist_file = dist/$(call python_get_sdistname,performance-metrics,$(project_rs_default),performance_metrics)

# Find the branch, sha, version that will be used to update the VERSION.json file
version_file = $(call python_get_git_version,performance-metrics,$(project_rs_default),performance_metrics)


clean_cmd = $(SHX) rm -rf 'build' '**/*.egg-info' '**/__pycache__' **/*.pyc '.mypy_cache' '.pytest_cache'
clean_wheel_cmd = $(clean_cmd) dist/*.whl
clean_sdist_cmd = $(clean_cmd) dist/*.tar.gz
clean_all_cmd = $(clean_cmd) dist

.PHONY: lint
lint:
Expand All @@ -20,12 +54,61 @@ teardown:

.PHONY: clean
clean:
rm -rf build dist *.egg-info .mypy_cache .pytest_cache src/performance_metrics.egg-info
$(clean_all_cmd)

.PHONY: wheel
wheel:
$(clean_wheel_cmd)
$(python) setup.py $(wheel_opts) bdist_wheel
rm -rf build
$(SHX) rm -rf build
$(SHX) ls dist

.PHONY: sdist
sdist: export OPENTRONS_PROJECT=$(project_rs_default)
sdist:
$(clean_sdist_cmd)
$(python) setup.py sdist
$(SHX) rm -rf build
$(SHX) ls dist

.PHONY: push-no-restart
push-no-restart: wheel
$(call push-python-package,$(host),$(ssh_key),$(ssh_opts),$(wheel_file))

.PHONY: push
push: push-no-restart
$(call restart-service,$(host),$(ssh_key),$(ssh_opts),"opentrons-robot-server")
SyntaxColoring marked this conversation as resolved.
Show resolved Hide resolved

.PHONY: push-no-restart-ot3
push-no-restart-ot3: sdist
$(call push-python-sdist,$(host),$(ssh_key),$(ssh_opts),$(sdist_file),/opt/opentrons-robot-server,performance_metrics,src,,$(version_file))

.PHONY: push-ot3
push-ot3: push-no-restart-ot3
$(call restart-service,$(host),$(ssh_key),$(ssh_opts),"opentrons-robot-server")

.PHONY: update-robot-version
update-robot-version:
DerekMaggio marked this conversation as resolved.
Show resolved Hide resolved
$(eval update_dict := '{"opentrons_api_version": "$(version)", "update_server_version": "$(version)", "robot_server_version": "$(version)", "server_utils_version": "$(version)", "opentrons_hardware_version": "$(version)"}')
$(call sync-version-file,$(host),$(ssh_key),$(ssh_opts),'$(update_dict)')
$(call restart-service,$(host),$(ssh_key),$(ssh_opts),"opentrons-robot-server")


.PHONY: set-performance-metrics-ff
set-performance-metrics-ff:
@curl \
-H "opentrons-version: *" \
-X POST $(host):31950/settings \
-H "content-type: application/json" \
-d '{"id": "enablePerformanceMetrics", "value": true}'

.PHONY: unset-performance-metrics-ff
unset-performance-metrics-ff:
@curl \
-H "opentrons-version: *" \
-X POST $(host):31950/settings \
-H "content-type: application/json" \
-d '{"id": "enablePerformanceMetrics", "value": false}'

.PHONY: test
test:
Expand Down
68 changes: 67 additions & 1 deletion performance-metrics/README.md
Original file line number Diff line number Diff line change
@@ -1,3 +1,69 @@
# Performance Metrics

Project to gather various performance metrics for the Opentrons Flex.
Library to gather various performance metrics for the Opentrons Flex.

Currently being imported inside of `opentrons.util.performance_helpers` which defines
helper function used inside other projects

## Setup

It is assumed that you already have the other projects in the monorepo setup correctly.

```bash
make -C performance-metrics setup
```

### Testing against OT-2 Dev Server

```bash
make -C robot-server dev-ot2
```

### Testing against real OT-2

To push development packages to OT-2 run the following command from the root directory of this repo (assumes you have the `host` environment variable set)

```bash
make -C performance-metrics push-no-restart host=<ot2-ip>
make -C api push-no-restart host=<ot2-ip>
make -C robot-server push host=<ot2-ip>
```

### Testing against Flex Dev Server

```bash
make -C robot-server dev-flex
```

### Testing against real Flex

```bash
make -C performance-metrics push-no-restart-ot3 host=<flex-ip>
make -C api push-no-restart-ot3 host=<flex-ip>
make -C robot-server push-ot3 host=<flex-ip>
```

### Extra step when running against real robots

Once this is done you might need to hack getting your robot and app to think they are on the same version.
Go to your app -> Settings -> General and find your app version
SyntaxColoring marked this conversation as resolved.
Show resolved Hide resolved

Then run

```bash
make -C performance-metrics update-robot-version version=<app-version> host=<robot-ip>
```

### Enabling performance-metrics feature flag

Performance metrics usage is hidden behind a feature flag. To enable it run the following command (assumes you have the `host` environment variable set):
DerekMaggio marked this conversation as resolved.
Show resolved Hide resolved

```bash
make set-performance-metrics-ff host=<ip>
```

To disable it run:

```bash
make unset-performance-metrics-ff host=<ip>
```
11 changes: 11 additions & 0 deletions performance-metrics/setup.cfg
DerekMaggio marked this conversation as resolved.
Show resolved Hide resolved
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
[bdist_wheel]
universal = 1

[metadata]
license_files = ../LICENSE

[pep8]
ignore = E221,E222

[aliases]
test=pytest
4 changes: 1 addition & 3 deletions performance-metrics/setup.py
Original file line number Diff line number Diff line change
Expand Up @@ -53,9 +53,7 @@ def get_version():
KEYWORDS = ["robots", "protocols", "synbio", "pcr", "automation", "lab"]
DESCRIPTION = "Library for working with performance metrics on the Opentrons robots"
PACKAGES = find_packages(where="src", exclude=["tests.*", "tests"])
INSTALL_REQUIRES = [
f"opentrons-shared-data=={VERSION}",
]
INSTALL_REQUIRES = []


def read(*parts):
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -25,7 +25,7 @@ class RawContextData(SupportsCSVStorage):
@classmethod
def headers(self) -> typing.Tuple[str, str, str]:
"""Returns the headers for the raw context data."""
return ("state_id", "function_start_time", "duration")
return ("state_name", "function_start_time", "duration")

def csv_row(self) -> typing.Tuple[str, int, int]:
"""Returns the raw context data as a string."""
Expand Down
1 change: 1 addition & 0 deletions performance-metrics/src/performance_metrics/types.py
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@

RobotContextState = typing.Literal[
"ANALYZING_PROTOCOL",
"GETTING_CACHED_ANALYSIS",
"RUNNING_PROTOCOL",
"CALIBRATING",
"ROBOT_STARTING_UP",
Expand Down
1 change: 0 additions & 1 deletion robot-server/Pipfile
Original file line number Diff line number Diff line change
Expand Up @@ -36,7 +36,6 @@ sqlalchemy2-stubs = "==0.0.2a21"
# limited by tavern
python-box = "==6.1.0"
types-paho-mqtt = "==1.6.0.20240106"
performance-metrics = {file = "../performance-metrics", editable = true}
DerekMaggio marked this conversation as resolved.
Show resolved Hide resolved
pyusb = "==1.2.1"

[packages]
Expand Down
Loading
Loading